using System;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.HtmlControls;
using System.Collections;
using System.Globalization;
using System.Text;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Reflection;

using BizElements.BusinessLayer;

namespace BizElements.Web
{
    /// <summary>
    /// UI control which provides values for the <see cref="ISearchEngine"/> object's filter.
    /// </summary>
    [ToolboxData("<{0}:SearchFilter runat=server></{0}:SearchFilter>")]
    [DesignerAttribute(typeof(SearchFilterDesigner), typeof(System.ComponentModel.Design.IDesigner))]
    public class SearchFilter : WebControl, INamingContainer, ISearchFilter
    {
        #region Constants.

        /// <summary>
        /// A character token (space character) which delimites words/phrases assigned to <see cref="ISearchEngineField.Values"/> property.
        /// </summary>
        /// <remarks>Splits user entry into multiple values when <see cref="AnyWordOrPhraseEnabled"/> is set to <b>true</b>.</remarks>
        /// <seealso cref="PhraseMarker"/>
        public const char WordDelimiter = ' ';

        /// <summary>
        /// A character token (quote character) which represents the beginning and the end of a phrase.
        /// </summary>
        /// <remarks>A phrase may contain more than one word.</remarks>
        /// <seealso cref="WordDelimiter"/>
        public const char PhraseMarker = '"';

        const int IntervalToggleImgWidth = 20;
        const string ToggleAdvancedOptionsButtonId = "btnToggleAdvOptions";

        #endregion

        #region Controls.

        private const string EndValueSuffix = "_EndValue";

        Table tblControlContainer = new Table();

        ButtonType searchButtonType = ButtonType.Button;
        bool searchButtonVisible = true;
        Button btnSearch = new Button();
        TemplateButton btnSearchCustom = new TemplateButton();

        ButtonType resetButtonType = ButtonType.Button;
        bool resetButtonVisible = true;
        Button btnReset = new Button();
        TemplateButton btnResetCustom = new TemplateButton();

        ButtonType saveFilterForActorButtonType = ButtonType.Button;
        bool saveFilterForActorButtonVisible;
        Button btnSaveFilterForActor = new Button();
        TemplateButton btnSaveCustom = new TemplateButton();

        ButtonType findFilterForActorButtonType = ButtonType.Button;
        bool findFilterButtonVisible;
        Button btnFindFilterForActor = new Button();
        TemplateButton btnFindFilterForActorCustom = new TemplateButton();

        DropDownList cmbOpenPredefinedFilter = new DropDownList();

        #endregion

        #region Fields.

        // Behavior.
        ISearchEngine engine;
        IMessageFormatter formatter = new MessageFormatter();
        bool invertFilterEnabled;
        bool addWithOrEnabled;
        IDictionary<string, string> predefinedSearchEngineFieldsStates = new Dictionary<string, string>();        
        bool overwriteAutoPopulatedFields;
        bool overwriteHiddenFields;
        bool resetAutoPopulatedFields;
        bool resetHiddenFields;
        bool anyWordOrPhraseEnabled;
        bool ignorePhrases;        
        string bannedChars;
        int maxWordCount;
        bool textSearchModeSelectionEnabled;
        bool isNullEnabled;        

        // Appearance.        
        Unit fieldControlWidth;
        string dropDownListCssClass;        
        string textBoxCssClass;
        string checkBoxCssClass;
        string addWithOrText;
        string addWithOrToolTip;        
        string invertFilterText;
        string invertFilterToolTip;        
        string collapseIntervalImageUrl;
        string expandIntervalImageUrl;
        string openPredefinedFilterCaption;
        string anyWordOrPhraseText;
        string anyWordOrPhraseToolTip;        
        string textSearchModeDefaultCaption;
        string textSearchModeAnywhereInTextCaption;        
        string textSearchModeBeginningOfTextCaption;        
        string textSearchModeExactMatchCaption;
        string isNullText;
        string isNullToolTip;
        string showAdvancedOptionsCaption = "Show advanced options";
        string hideAdvancedOptionsCaption = "Hide advanced options";
        bool advancedOptionsVisible = true;
        bool toggleAdvancedOptionsButtonVisible;

        // Calendar.
        DateTime calendarLowerBoundDate;
        DateTime calendarUpperboundDate;
        string calendarImageUrl;   

        #endregion

        #region Constructor.

        /// <summary>
        /// Initializes a new instance of the <b>SearchFilter</b> class.
        /// </summary>
        public SearchFilter()
        {
            this.btnSaveFilterForActor.Visible = false;
            this.btnFindFilterForActor.Visible = false;
            this.cmbOpenPredefinedFilter.Visible = false;
        }

        #endregion
       
        #region CreateChildControls.

        /// <summary>
        /// Called by the ASP.NET page framework to notify server controls that use composition-based 
        /// implementation to create any child controls they contain in preparation for posting back or rendering. 
        /// </summary>
        protected override void CreateChildControls()
        {
            // Main controls are created by class initializers to ensure that instances always exist.
            this.Controls.Clear();

            // Main table.
            this.tblControlContainer.CellSpacing = 1;
            this.tblControlContainer.CellPadding = 0;
            this.tblControlContainer.BorderWidth = 0;
            this.Controls.Add(this.tblControlContainer);

            OverrideFieldProperties();
            this.ControlSharing.LinkToEngine(this.Engine);

            // One row per search field/combination.
            StringCollection processedFields = new StringCollection();
            bool isFirstField = true;
            foreach (ISearchEngineField field in this.OrderedFields)
            {
                // It might have already been processed in a control share case.
                if (processedFields.Contains(field.BindingName))
                    continue;

                bool sharesInputControlWithOtherFields = this.ControlSharing.ContainsField(field.BindingName);
                TableRow row = CreateSearchFieldRow(field);
                this.tblControlContainer.Rows.Add(row);
                if (field.EndValue != null)
                    ExpandEndValueCell(field);

                if (sharesInputControlWithOtherFields)
                {
                    SharedControlDef sharing = this.ControlSharing.GetCombination(field.BindingName);
                    foreach (string combinedField in sharing.Fields)
                        processedFields.Add(combinedField);
                }
                else
                {
                    processedFields.Add(field.BindingName);
                }

                if (isFirstField)
                {
                    Control focusedCtl = GetControl(field, false);
                    if (focusedCtl != null)
                        focusedCtl.Focus();
                }
                isFirstField = false;
            }

            CreateToggleAdvancedOptionsRow();

            // Action buttons.
            TableCell cellActionButtons = new TableCell();
            cellActionButtons.ColumnSpan = (this.tblControlContainer.Rows.Count > 0) ? this.tblControlContainer.Rows[0].Cells.Count : 1;
            int idxActionButtonsRow = this.tblControlContainer.Rows.Add(new TableRow());
            this.tblControlContainer.Rows[idxActionButtonsRow].Cells.Add(cellActionButtons);
            
            Table tblActionButtons = new Table();
            cellActionButtons.Controls.Add(tblActionButtons);
            tblActionButtons.CellPadding = 0;
            tblActionButtons.CellSpacing = 2;
            tblActionButtons.Width = new Unit("100%");
            TableRow rowActions = new TableRow();
            tblActionButtons.Rows.Add(rowActions);

            // Search button.
            TableCell cellSearch = new TableCell();
            rowActions.Cells.Add(cellSearch);
            cellSearch.Controls.Add(this.btnSearch);
            cellSearch.Controls.Add(this.btnSearchCustom);
            this.btnSearch.Click += new EventHandler(btnSearch_Click);
            this.btnSearchCustom.Click += new EventHandler(btnSearch_Click);

            // Reset button.
            TableCell cellReset = new TableCell();
            rowActions.Cells.Add(cellReset);
            cellReset.Controls.Add(this.btnReset);
            cellReset.Controls.Add(this.btnResetCustom);
            this.btnReset.Click += new EventHandler(btnReset_Click);            
            this.btnResetCustom.Click += new EventHandler(btnReset_Click);

            // Save filter for actor button.
            TableCell cellSave = new TableCell();
            rowActions.Cells.Add(cellSave);
            cellSave.Controls.Add(this.btnSaveFilterForActor);
            cellSave.Controls.Add(this.btnSaveCustom);
            this.btnSaveFilterForActor.Click += new EventHandler(btnSaveFilterForActor_Click);
            this.btnSaveCustom.Click += new EventHandler(btnSaveFilterForActor_Click);

            // Find filter for actor button.
            TableCell cellFind = new TableCell();
            rowActions.Cells.Add(cellFind);
            cellFind.Controls.Add(this.btnFindFilterForActor);
            cellFind.Controls.Add(this.btnFindFilterForActorCustom);
            this.btnFindFilterForActor.Click += new EventHandler(btnFindFilterForActor_Click);
            this.btnFindFilterForActorCustom.Click += new EventHandler(btnFindFilterForActor_Click);

            // Open predefined filter DDL.
            TableCell cellOpenFilter = new TableCell();
            rowActions.Cells.Add(cellOpenFilter);
            cellOpenFilter.Controls.Add(this.cmbOpenPredefinedFilter);
            this.cmbOpenPredefinedFilter.AutoPostBack = true;
            this.cmbOpenPredefinedFilter.SelectedIndexChanged += new EventHandler(cmbOpenPredefinedFilter_SelectedIndexChanged);

            TableCell farRightCell = new TableCell();
            farRightCell.Width = new Unit("100%");
            rowActions.Cells.Add(farRightCell);

            // Hide action-button section if all buttons are disabled.
            bool atLeastOneButtonIsVisible = (this.SearchButtonVisible || this.ResetButtonVisible || this.SaveButtonVisible || this.OpenPredefinedFilterVisible);
            if (!atLeastOneButtonIsVisible)
                this.tblControlContainer.Rows[idxActionButtonsRow].Visible = false;
        }

        private TableRow CreateSearchFieldRow(ISearchEngineField field)
        {
            // Creates a tablerow that contains controls for the given field.
            TableRow row = new TableRow();
            row.ID = GetRowId(field);
            bool shouldHideAdvancedField = !this.AdvancedOptionsVisible && (field.Usage == SearchFieldUsage.AdvancedSearch);
            if (shouldHideAdvancedField)
                row.Style["display"] = "none";

            row.Cells.Add(CreateLabelCell(field));
            
            // Begin and end value controls.
            TableCell[] beginEndButtonCells = CreateSearchFieldCells(field);
            foreach (TableCell cell in beginEndButtonCells)
                row.Cells.Add(cell);

            if (field.IsHidden)
                row.Style["display"] = "none";

            return row;
        }

        private TableCell[] CreateSearchFieldCells(ISearchEngineField field)
        {
            // when used for interval search, end value control can be toggled.
            // Two controls. Begin and end values.
            // HTML code:
            //      <td> [Begin value] </td>
            //		<td>
            //          <table>
            //				<tr>
            //					<td> - [End value] [IsValidCheckBox] <td>
            //					<td> [Toggle button] </td>
            //                  <td> [And/Or] [Negate] </td> 
            //				</tr>
            //			</table>
            //      </td>

            TableCell beginCell = new TableCell();
            beginCell.Controls.Add(CreateFieldControl(field, false));

            TableCell rightCell = new TableCell();
            Table controlTable = new Table();
            rightCell.Controls.Add(controlTable);
            controlTable.CellSpacing = 0;
            controlTable.CellPadding = 0;
            controlTable.BorderWidth = 0;

            TableRow controlRow = new TableRow();
            controlTable.Rows.Add(controlRow);

            if (IntervalFlagsUtil.ShouldCreateInterval(field, this.IntervalCriteria))
            {
                CreateEndValueCell(field, controlRow);
                CreateToggleEndValueCell(field, controlRow);
            }
            else
            {
                // Add two empty cells to match column count as required by fields which allow interval search.
                controlRow.Cells.Add(new TableCell() { Width = new Unit(IntervalToggleImgWidth) });
                controlRow.Cells.Add(new TableCell());
            }

            CreatePredefinedNameValueSetsCell(field, controlRow);
            CreateAdvancedOptionsCell(field, controlRow);

            return new TableCell[] { beginCell, rightCell };
        }

        private void CreatePredefinedNameValueSetsCell(ISearchEngineField field, TableRow controlRow)
        {
            // Predefined named value sets.
            if (field.ValueSets != null && field.ValueSets.Count > 0)
            {
                TableCell valueSetsCell = new TableCell();
                controlRow.Cells.Add(valueSetsCell);
                valueSetsCell.Controls.Add(CreateValueSetDdl(field));
            }
        }

        private void CreateToggleEndValueCell(ISearchEngineField field, TableRow controlRow)
        {
            // Button toggles end cell visibility and sets the visibility (isValid) status in a hidden checkbox.
            RegisterToggleControlVisibilityByButtonScript();
            TableCell toggleBtnCell = new TableCell();
            controlRow.Cells.Add(toggleBtnCell);

            HtmlImage imgToggle = new HtmlImage();
            imgToggle.Border = 0;
            imgToggle.Src = this.ExpandIntervalImageUrl;
            imgToggle.Attributes["onclick"] = GetToggleControlVisibilityByButtonScriptName() + "('" + GetEndValueCellId(field, true) + "', '" + GetToggleButtonId(field, true) + "', '" + GetIsValidEndValueCheckBoxId(field, true) + "'); ";
            imgToggle.ID = GetToggleButtonId(field, false);
            imgToggle.Width = IntervalToggleImgWidth;
            imgToggle.Height = IntervalToggleImgWidth;
            imgToggle.Style["cursor"] = "pointer";
            toggleBtnCell.Controls.Add(imgToggle);
        }

        private void CreateEndValueCell(ISearchEngineField field, TableRow controlRow)
        {
            // End cell can be toggled in client-side jscript. Hidden by default.
            // Contains a hyphen, end-value control and a hidden check-box that specifies whether
            // the value contained in end control is valid (visible).
            TableCell endCell = new TableCell();
            endCell.ID = GetEndValueCellId(field, false);
            endCell.Style["display"] = "none";
            endCell.Wrap = false;
            Label hyphen = new Label();
            hyphen.Text = " -  ";
            endCell.Controls.Add(hyphen);
            endCell.Controls.Add(CreateFieldControl(field, true));
            CheckBox chkIsValid = new CheckBox();
            chkIsValid.ID = GetIsValidEndValueCheckBoxId(field, false);
            chkIsValid.Style["display"] = "none";
            chkIsValid.EnableViewState = true;
            endCell.Controls.Add(chkIsValid);
            controlRow.Cells.Add(endCell);
        }

        private string GetToggleControlVisibilityByButtonScriptName()
        {
            return this.ClientID + "_ToggleControlVisibilityByButton";
        }

        private void RegisterToggleControlVisibilityByButtonScript()
        {
            // Toggles end value controls' visibility.
            // Form layout when end controls are hidden.
            // Country:	[TextBox] [>>]
            // Form layout when end control are visible.
            // Country	[TextBox] - [TextBox] [<<]

            string functionName = GetToggleControlVisibilityByButtonScriptName();
            if (Page.ClientScript.IsClientScriptBlockRegistered(typeof(Page), functionName))
                return;

            string jsToggle = "";
            jsToggle += "\n" + "function " + functionName + "(controlId, buttonId, isValidCheckBoxId)";
            jsToggle += "\n" + "{";
            jsToggle += "\n" + "    var ctl = document.getElementById(controlId);";
            jsToggle += "\n" + "    var btn = document.getElementById(buttonId);";
            jsToggle += "\n" + "    var isValidChk = document.getElementById(isValidCheckBoxId);";
            jsToggle += "\n" + "";
            jsToggle += "\n" + "    if (ctl == null  ||  btn == null  ||  isValidChk == null)";
            jsToggle += "\n" + "        return;";
            jsToggle += "\n" + "";
            jsToggle += "\n" + "    if (ctl.style.display == 'none')";
            jsToggle += "\n" + "    {";
            jsToggle += "\n" + "        ctl.style.display = 'block';";
            jsToggle += "\n" + "        btn.src = '" + GetAbsoluteUrl(this.CollapseIntervalImageUrl) + "';";
            // Indicate that the specified (visible) control contains a valid value.
            jsToggle += "\n" + "        isValidChk.checked = true;";
            jsToggle += "\n" + "    }";
            jsToggle += "\n" + "    else";
            jsToggle += "\n" + "    {";
            jsToggle += "\n" + "        ctl.style.display = 'none';";
            jsToggle += "\n" + "        btn.src = '" + GetAbsoluteUrl(this.ExpandIntervalImageUrl) + "';";
            // Indicate that the specified (hidden) control DOESN'T contain a valid value.
            jsToggle += "\n" + "        isValidChk.checked = false;";
            jsToggle += "\n" + "    }";
            jsToggle += "\n" + "}";

            Page.ClientScript.RegisterClientScriptBlock(typeof(Page), functionName, jsToggle, true);
        }

        private static string GetAbsoluteUrl(string url)
        {
            return WebUtil.ResolveUrl(url);
        }

        #endregion

        #region Labels - field captions.

        private TableCell CreateLabelCell(ISearchEngineField field)
        {
            // Creates a label cell for the given field.
            string fieldCaption;
            if (this.ControlSharing.ContainsField(field.BindingName))
                fieldCaption = this.ControlSharing.GetCombination(field.BindingName).Caption;
            else
                fieldCaption = field.Caption;

            TableCell labelCell = new TableCell();
            labelCell.Text = fieldCaption + ":&nbsp;";
            labelCell.HorizontalAlign = HorizontalAlign.Right;

            return labelCell;
        }

        #endregion

        #region Negate, AddWithOr, any word/phrase, text mode, value sets, IsNull controls.

        private CheckBox CreateNegateCheckBox(ISearchEngineField field)
        {
            CheckBox chkNegate = new CheckBox();
            chkNegate.ID = GetNegateCheckBoxId(field);
            chkNegate.Text = this.InvertFilterText;
            chkNegate.CssClass = this.CheckBoxCssClass;
            chkNegate.ToolTip = this.InvertFilterToolTip;
            chkNegate.Checked = field.Negate;
            bool hasWhiteSpace = string.IsNullOrEmpty(this.InvertFilterText) ? false : this.InvertFilterText.Contains(" ");
            if (hasWhiteSpace)
                chkNegate.Style["white-space"] = "nowrap";

            return chkNegate;
        }

        private CheckBox CreateAddWithOrCheckBox(ISearchEngineField field)
        {
            CheckBox chkAddWithOr = new CheckBox();
            chkAddWithOr.ID = GetAddWithOrCheckBoxId(field);
            chkAddWithOr.Text = this.AddWithOrText;
            chkAddWithOr.CssClass = this.CheckBoxCssClass;
            chkAddWithOr.ToolTip = this.AddWithOrToolTip;
            chkAddWithOr.Checked = field.AddWithOr;
            bool hasWhiteSpace = string.IsNullOrEmpty(this.AddWithOrText) ? false : this.AddWithOrText.Contains(" ");
            if (hasWhiteSpace)
                chkAddWithOr.Style["white-space"] = "nowrap";

            return chkAddWithOr;
        }

        private CheckBox CreateAnyWordCheckBox(ISearchEngineField field)
        {
            CheckBox chkAnyWord = new CheckBox();
            chkAnyWord.ID = GetAnyWordCheckBoxId(field);
            chkAnyWord.Text = this.AnyWordOrPhraseText;
            chkAnyWord.CssClass = this.CheckBoxCssClass;
            chkAnyWord.ToolTip = this.AnyWordOrPhraseToolTip;
            bool hasWhiteSpace = string.IsNullOrEmpty(this.AnyWordOrPhraseText) ? false : this.AnyWordOrPhraseText.Contains(" ");
            if (hasWhiteSpace)
                chkAnyWord.Style["white-space"] = "nowrap";

            return chkAnyWord;
        }

        private DropDownList CreateValueSetDdl(ISearchEngineField field)
        {
            DropDownList ddl = new DropDownList();
            ddl.ID = GetValueSetsDdlId(field);
            ddl.CssClass = this.DropDownListCssClass;
            ddl.Width = this.FieldControlWidth;

            ddl.DataSource = field.ValueSets;
            ddl.DataValueField = "Key";
            ddl.DataTextField = "Name";
            ddl.DataBind();
            ddl.Items.Insert(0, new ListItem("", ""));
            if (field.SelectedValueSetKey != null)
            {
                try 
                { 
                    if (ddl.Items.FindByValue(field.SelectedValueSetKey) != null)
                        ddl.SelectedValue = field.SelectedValueSetKey; 
                }
                catch { }
            }

            return ddl;
        }

        private DropDownList CreateSelectTextModeDdl(ISearchEngineField field)
        {
            DropDownList ddl = new DropDownList();
            ddl.ID = GetSelectTextModeDdlId(field);
            ddl.CssClass = this.DropDownListCssClass;

            bool allowsDefault = (Array.IndexOf<TextSearchMode>(field.AllowedTextSearchModes, TextSearchMode.Default) >= 0);
            if (allowsDefault)
                ddl.Items.Add(new ListItem(this.TextSearchModeDefaultCaption, TextSearchMode.Default.ToString()));

            bool allowsBeginning = (Array.IndexOf<TextSearchMode>(field.AllowedTextSearchModes, TextSearchMode.BeginningOfText) >= 0);
            if (allowsBeginning)
                ddl.Items.Add(new ListItem(this.TextSearchModeBeginningOfTextCaption, TextSearchMode.BeginningOfText.ToString()));

            bool allowsAnywhere = (Array.IndexOf<TextSearchMode>(field.AllowedTextSearchModes, TextSearchMode.AnywhereInText) >= 0);
            if (allowsAnywhere)
                ddl.Items.Add(new ListItem(this.TextSearchModeAnywhereInTextCaption, TextSearchMode.AnywhereInText.ToString()));

            bool allowsExact = (Array.IndexOf<TextSearchMode>(field.AllowedTextSearchModes, TextSearchMode.ExactMatch) >= 0);
            if (allowsExact)
                ddl.Items.Add(new ListItem(this.TextSearchModeExactMatchCaption, TextSearchMode.ExactMatch.ToString()));

            try { ddl.SelectedValue = field.SelectedTextSearchMode.ToString(); }
            catch { ddl.SelectedIndex = 0; }

            return ddl;
        }

        private CheckBox CreateIsNullCheckBox(ISearchEngineField field)
        {
            CheckBox chkIsNull = new CheckBox();
            chkIsNull.ID = GetIsNullCheckBoxId(field);
            chkIsNull.Text = this.IsNullText;
            chkIsNull.CssClass = this.CheckBoxCssClass;
            chkIsNull.ToolTip = this.IsNullToolTip;
            chkIsNull.Checked = field.IsNull;
            bool hasWhiteSpace = string.IsNullOrEmpty(this.IsNullText) ? false : this.IsNullText.Contains(" ");
            if (hasWhiteSpace)
                chkIsNull.Style["white-space"] = "nowrap";

            return chkIsNull;
        }

        #endregion        

        #region Lookup - common.

        /// <summary>
        /// Enumerates supported lookup controls which implement <see cref="IBoundLookupControl"/> interface.
        /// </summary>
        public enum LookupControlType
        {
            #region Class names.

            /// <summary>
            /// Specifies <see cref="BoundLookup"/> control.
            /// </summary>
            BoundLookup,

            /// <summary>
            /// Specifies <see cref="BoundAutoComplete"/> control.
            /// </summary>
            BoundAutoComplete

            #endregion
        }

        LookupControlType lookupType = LookupControlType.BoundLookup; 

        /// <summary>
        /// Gets or sets the type of lookup control to be rendered when a search engine builder 
        /// is specified for a search field (see <see cref="ISearchEngineField.LookupBuilder"/>).
        /// </summary>
        [Category("Behavior")]
        [DefaultValue(typeof(LookupControlType), "BoundLookup")]
        [Description("Specifies the type of lookup control to be rendered when a search engine builder is specified for a search field.")]
        public LookupControlType LookupType
        {
          get { return this.lookupType; }
          set { this.lookupType = value; }
        }

        private Control CreateSearchClient(ISearchEngineField field, bool isEndValueControl)
        {
            switch (this.LookupType)
            {
                case LookupControlType.BoundLookup:
                    return CreateLookupControl(field, isEndValueControl);
                case LookupControlType.BoundAutoComplete:
                    return CreateAutoCompleteControl(field, isEndValueControl);
                default:
                    throw new ArgumentException("The specified search client type is not supported: " + lookupType.ToString());
            }
        }

        #endregion

        #region BoundLookup - dedicated search form.

        Delegates.Function<string, ISearchEngineField> lookupTextQueryKeyGenerator;

        /// <summary>
        /// Gets or sets the statix method which builds the values for <see cref="BoundLookup.TextQueryKey"/> properties of the generated <see cref="BoundLookup"/> controls.
        /// </summary>
        /// <value>Method which accepts <see cref="ISearchEngineField"/> parameter and returns <see cref="System.String"/>
        /// or <b>null</b> to use default implementation.</value>
        /// <remarks>Default implementation builds the key from <see cref="LookupTextQueryKeyPrefix"/> and
        /// <see cref="IBindableField.BindingName"/>.</remarks>
        [Browsable(false)]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public virtual Delegates.Function<string, ISearchEngineField> LookupTextQueryKeyGenerator
        {
            get { return lookupTextQueryKeyGenerator; }
            set 
            {
                if (value != null && !value.Method.IsStatic)
                    throw new ArgumentException("The provided method must be static.");

                lookupTextQueryKeyGenerator = value; 
            }
        }

        string lookupSkinID;

        /// <summary>
        /// Gets or sets the <see cref="Control.SkinID"/> property for the generated <see cref="BoundLookup"/> controls.
        /// </summary>
        [Category("Appearance")]
        [Description("Skin applied to generated BoundLookup controls.")]
        [DefaultValue(null)]
        public virtual string LookupSkinID
        {
            get { return lookupSkinID; }
            set { lookupSkinID = value; }
        }

        bool lookupReadOnly;

        /// <summary>
        /// Gets or sets the <see cref="TextBox.ReadOnly"/> property for the generated <see cref="BoundLookup"/> controls.
        /// </summary>
        [Category("Behavior")]
        [Description("Whether the generated BoundLookup controls allow user input.")]
        [DefaultValue(false)]
        public virtual bool LookupReadOnly
        {
            get { return lookupReadOnly; }
            set { lookupReadOnly = value; }
        }

        string lookupTextQueryKeyPrefix;

        /// <summary>
        /// Gets or sets the prefix used to build the values for <see cref="BoundLookup.TextQueryKey"/> properties of the generated <see cref="BoundLookup"/> controls.
        /// </summary>
        /// <remarks>Default implementation builds the key from <see cref="LookupTextQueryKeyPrefix"/> and
        /// <see cref="IBindableField.BindingName"/>.</remarks>
        [Category("Behavior")]
        [Description("Prefix used to build the values for TextQueryKey properties of the generated BoundLookup controls.")]
        [DefaultValue(null)]
        public virtual string LookupTextQueryKeyPrefix
        {
            get { return lookupTextQueryKeyPrefix; }
            set { lookupTextQueryKeyPrefix = value; }
        }

        string lookupBuilderSkinID;

        /// <summary>
        /// Gets or sets the <see cref="Control.SkinID"/> property for the generated <see cref="SearchProviderBuilder"/> components.
        /// </summary>
        [Category("Appearance")]
        [Description("Skin applied to generated SearchProviderBuilder controls.")]
        [DefaultValue(null)]
        public virtual string LookupBuilderSkinID
        {
            get { return lookupBuilderSkinID; }
            set { lookupBuilderSkinID = value; }
        }

        string lookupFilterComponentAssembly;

        /// <summary>
        /// Gets or sets the full name od the assembly which contains the <see cref="ISearchFilter"/> class which is instantiated for lookup fields.
        /// </summary>
        /// <value>The long form of the assembly name or <b>null</b> if no filter components are to be generated.</value>
        [Category("Behavior")]
        [Description("Full name of the assembly which contains the ISearchFilter class which is instantiated for lookup fields.")]
        [DefaultValue(null)]
        public virtual string LookupFilterComponentAssembly
        {
            get { return lookupFilterComponentAssembly; }
            set { lookupFilterComponentAssembly = value; }
        }

        string lookupFilterComponentClass;

        /// <summary>
        /// Gets or sets the full name of the <see cref="ISearchFilter"/> class which is instantiated for lookup fields.
        /// </summary>
        /// <value>The <see cref="Type.FullName"/> of the class to locate in the <see cref="LookupFilterComponentAssembly"/>
        /// or <b>null</b> if no filter components are to be generated.</value>
        [Category("Behavior")]
        [Description("Full name of the ISearchFilter class which is instantiated for lookup fields.")]
        [DefaultValue(null)]
        public virtual string LookupFilterComponentClass
        {
            get { return lookupFilterComponentClass; }
            set { lookupFilterComponentClass = value; }
        }

        string lookupFilterComponentSkinID;

        /// <summary>
        /// Gets or sets the <see cref="Control.SkinID"/> property for the generated <see cref="ISearchFilter"/> components.
        /// </summary>
        [Category("Appearance")]
        [Description("Skin applied to generated ISearchFilter controls.")]
        [DefaultValue(null)]
        public virtual string LookupFilterComponentSkinID
        {
            get { return lookupFilterComponentSkinID; }
            set { lookupFilterComponentSkinID = value; }
        }

        string lookupViewComponentAssembly;

        /// <summary>
        /// Gets or sets the full name od the assembly which contains the <see cref="ISearchView"/> class which is instantiated for lookup fields.
        /// </summary>
        /// <value>The long form of the assembly name or <b>null</b> if no view components are to be generated.</value>
        [Category("Behavior")]
        [Description("Full name od the assembly which contains the ISearchView class which is instantiated for lookup fields.")]
        [DefaultValue(null)]
        public virtual string LookupViewComponentAssembly
        {
            get { return lookupViewComponentAssembly; }
            set { lookupViewComponentAssembly = value; }
        }

        string lookupViewComponentClass;

        /// <summary>
        /// Gets or sets the full name of the <see cref="ISearchView"/> class which is instantiated for lookup fields.
        /// </summary>
        /// <value>The <see cref="Type.FullName"/> of the class to locate in the <see cref="LookupViewComponentAssembly"/>
        /// or <b>null</b> if no view components are to be generated.</value>
        [Category("Behavior")]
        [Description("Full name of the ISearchView class which is instantiated for lookup fields.")]
        [DefaultValue(null)]
        public virtual string LookupViewComponentClass
        {
            get { return lookupViewComponentClass; }
            set { lookupViewComponentClass = value; }
        }

        string lookupViewComponentSkinID;

        /// <summary>
        /// Gets or sets the <see cref="Control.SkinID"/> property for the generated <see cref="ISearchView"/> components.
        /// </summary>
        [Category("Appearance")]
        [Description("Skin applied to generated ISearchView controls.")]
        [DefaultValue(null)]
        public virtual string LookupViewComponentSkinID
        {
            get { return lookupViewComponentSkinID; }
            set { lookupViewComponentSkinID = value; }
        }

        List<Pair<ISearchEngineField, BoundLookup>> lookupsToRegister = new List<Pair<ISearchEngineField, BoundLookup>>();

        private void RegisterLookup(ISearchEngineField field, BoundLookup lookup)
        {
            SearchProviderBuilder lookupBuilder = new SearchProviderBuilder();
            if (!string.IsNullOrEmpty(this.LookupBuilderSkinID))
                lookupBuilder.SkinID = this.LookupBuilderSkinID;
            lookupBuilder.ApplyStyleSheetSkin(this.Page);

            Control filter = ReflectionUtil.TryCreateInstance(this.LookupFilterComponentAssembly, this.LookupFilterComponentClass) as Control;
            if (filter != null)
            {
                if (!string.IsNullOrEmpty(this.LookupFilterComponentSkinID))
                    filter.SkinID = this.LookupFilterComponentSkinID;
                
                filter.ApplyStyleSheetSkin(this.Page);
                lookupBuilder.Controls.Add(filter);
            }

            Control view = ReflectionUtil.TryCreateInstance(this.LookupViewComponentAssembly, this.LookupViewComponentClass) as Control;
            if (view != null)
            {
                if (!string.IsNullOrEmpty(this.LookupViewComponentSkinID))
                    view.SkinID = this.LookupViewComponentSkinID;

                view.ApplyStyleSheetSkin(this.Page);
                lookupBuilder.Controls.Add(view);
            }

            lookupBuilder.EngineBuilder = field.LookupBuilder;
            string providerKey = "BizElements.BusinessLayer.SearchFilter.e563dec0b9a54546a6d642f4c532cce5." + field.BindingName + "." + field.LookupBuilder.GetType().ToString();
            // Same field is stored in SelectedValue and Text property.
            lookupBuilder.RegisterForClient(providerKey, lookup, true, new string[] { field.BindingName, field.BindingName });
        }

        private Control CreateLookupControl(ISearchEngineField field, bool isEndValueControl)
        {
            BoundLookup lookup = new BoundLookup();
            lookup.ID = GetLookupId(field, isEndValueControl);
            lookup.Width = this.FieldControlWidth;
            lookup.ReadOnly = LookupReadOnly;
            lookup.SkinID = this.LookupSkinID;
            lookup.TextQueryKey = (this.LookupTextQueryKeyGenerator != null) ? this.LookupTextQueryKeyGenerator(field) : GetLookupTextQueryKey(field);

            object selectedValue = (!isEndValueControl) ? field.BeginValue : field.EndValue;
            if (selectedValue != null)
            {
                lookup.SelectedValue = Convert.ToString(selectedValue, CultureInfo.InvariantCulture);
                lookup.Text = (this.Formatter != null) ? this.Formatter.Format(selectedValue) : Convert.ToString(selectedValue);
            }

            this.lookupsToRegister.Add(new Pair<ISearchEngineField, BoundLookup>(field, lookup));
            return lookup;
        }

        private string GetLookupTextQueryKey(ISearchEngineField field)
        {
            return this.LookupTextQueryKeyPrefix + field.BindingName;
        }

        #endregion

        #region AJAX lookups - BoundAutoComplete.

        string autoCompleteSkinID;

        /// <summary>
        /// Gets or sets the <see cref="Control.SkinID"/> property for the generated <see cref="BoundAutoComplete"/> controls.
        /// </summary>
        [Category("Appearance")]
        [Description("Skin applied to generated BoundAutoComplete controls.")]
        [DefaultValue(null)]
        public string AutoCompleteSkinID
        {
            get { return this.autoCompleteSkinID; }
            set { this.autoCompleteSkinID = value; }
        }

        List<Pair<ISearchEngineField, BoundAutoComplete>> autoCompletesToRegister = new List<Pair<ISearchEngineField, BoundAutoComplete>>();

        private void RegisterAutoComplete(ISearchEngineField field, BoundAutoComplete lookup)
        {
            SearchProviderBuilder lookupBuilder = new SearchProviderBuilder();
            if (!string.IsNullOrEmpty(this.AutoCompleteSkinID))
                lookup.SkinID = this.AutoCompleteSkinID;
            
            lookupBuilder.ApplyStyleSheetSkin(this.Page);
            lookupBuilder.EngineBuilder = field.LookupBuilder;
            string providerKey = "BizElements.BusinessLayer.SearchFilter.e563dec0b9a54546a6d642f4c532cce5." + field.BindingName + "." + field.LookupBuilder.GetType().ToString();
            // Same field is stored in SelectedValue and Text property.
            lookupBuilder.RegisterForClient(providerKey, lookup, true, new string[] { field.BindingName, field.BindingName });
        }

        private Control CreateAutoCompleteControl(ISearchEngineField field, bool isEndValueControl)
        {
            BoundAutoComplete lookup = new BoundAutoComplete();
            lookup.ID = GetLookupId(field, isEndValueControl);
            lookup.Width = this.FieldControlWidth;
            lookup.SkinID = this.AutoCompleteSkinID;

            object selectedValue = (!isEndValueControl) ? field.BeginValue : field.EndValue;
            if (selectedValue != null)
            {
                lookup.SelectedValue = Convert.ToString(selectedValue, CultureInfo.InvariantCulture);
                lookup.Text = (this.Formatter != null) ? this.Formatter.Format(selectedValue) : Convert.ToString(selectedValue);
            }

            this.autoCompletesToRegister.Add(new Pair<ISearchEngineField, BoundAutoComplete>(field, lookup));
            return lookup;
        }

        #endregion

        #region Field value controls - CreateFieldControl.

        private Control CreateFieldControl(ISearchEngineField field, bool isEndValueControl)
        {
            Control ctl;
            object selectedValue = (!isEndValueControl) ? field.BeginValue : field.EndValue;

            if (field.LookupBuilder != null)
            { 
                ctl = CreateSearchClient(field, isEndValueControl);
            }
            else if (field.CodeList != null)
            {
                ctl = CreateDropDownList(field, isEndValueControl, selectedValue);
            }
            else
            {
                if (field.DataType == typeof(DateTime))
                {
                    ctl = CreateCalendar(field, isEndValueControl, selectedValue);
                }
                else
                {
                    ctl = CreateTextBox(field, isEndValueControl, selectedValue);
                }
            }

            ctl.EnableViewState = true;
            return ctl;
        }

        private DropDownList CreateDropDownList(ISearchEngineField field, bool isEndValueControl, object selectedValue)
        {
            DropDownList ddl = new DropDownList();
            ddl.ID = GetDdlId(field, isEndValueControl);
            ddl.CssClass = this.DropDownListCssClass;
            ddl.Width = this.FieldControlWidth;

            // TODO: Check if the codelist has to be bound only on first load.
            ddl.DataSource = field.CodeList.DataSource;
            ddl.DataValueField = field.CodeList.ValueMember;
            ddl.DataTextField = field.CodeList.DisplayMember;
            ddl.DataBind();
            ddl.Items.Insert(0, new ListItem("", ""));
            if (selectedValue != null)
            {
                try { ddl.SelectedValue = Convert.ToString(selectedValue, CultureInfo.InvariantCulture); }
                catch { }
            }

            if (field.IsAutoPopulated || field.IsReadOnly)
                ddl.Attributes["onfocus"] = "javascript:this.blur(); ";
            return ddl;
        }

        private TextBox CreateTextBox(ISearchEngineField field, bool isEndValueControl, object selectedValue)
        {
            TextBox box = new TextBox();
            box.ID = GetTextBoxId(field, isEndValueControl);

            box.Width = this.FieldControlWidth;
            box.CssClass = this.TextBoxCssClass;

            if (selectedValue != null)
                box.Text = this.Formatter.Format(selectedValue);

            bool noTextInput = (field.IsAutoPopulated || field.IsReadOnly);
            if (noTextInput)
            {
                box.Attributes["onfocus"] = "javascript:this.blur(); ";
            }
            else
            {
                bool hasDefinedBannedChars = !string.IsNullOrEmpty(this.BannedChars);
                if (hasDefinedBannedChars)
                    box.Attributes["onkeypress"] = "return " + GetCancelBannedCharsScriptName() + "(event);";
            }
            return box;
        }

        private BoundCalendar CreateCalendar(ISearchEngineField field, bool isEndValueControl, object selectedValue)
        {
            BoundCalendar picker = new BoundCalendar();
            picker.ID = GetCalendarId(field, isEndValueControl);
            if (this.formatter.DateFormat != null)
                picker.Format = this.formatter.DateFormat;

            picker.StartDate = this.CalendarLowerBoundDate;
            picker.EndDate = this.CalendarUpperBoundDate;
            picker.ImageUrl = this.CalendarImageUrl;
            picker.TextBox.CssClass = this.TextBoxCssClass;

            if (selectedValue != null && selectedValue is DateTime)
                picker.SelectedValue = (DateTime)selectedValue;
            else
                picker.SelectedValue = null;

            if (field.IsAutoPopulated || field.IsReadOnly)
                picker.Enabled = false;

            return picker;
        }

        #endregion        

        #region ExpandEndValueCell, CollapseEndValueCell.

        private void ExpandEndValueCell(ISearchEngineField field)
        {
            string endCellId = GetEndValueCellId(field, false);
            TableCell endCell = FindControl(endCellId) as TableCell;
            if (endCell == null)
                return;

            endCell.Style["display"] = "block";
            CheckBox chkHasValidValue = (CheckBox)FindControl(GetIsValidEndValueCheckBoxId(field, false));
            chkHasValidValue.Checked = true;
            HtmlImage imgToggle = (HtmlImage)FindControl(GetToggleButtonId(field, false));
            imgToggle.Src = this.CollapseIntervalImageUrl;
        }

        private void CollapseEndValueCell(ISearchEngineField field)
        {
            string endCellId = GetEndValueCellId(field, false);
            TableCell endCell = FindControl(endCellId) as TableCell;
            if (endCell == null)
                return;

            endCell.Style["display"] = "none";
            CheckBox chkHasValidValue = (CheckBox)FindControl(GetIsValidEndValueCheckBoxId(field, false));
            chkHasValidValue.Checked = false;
            HtmlImage imgToggle = (HtmlImage)FindControl(GetToggleButtonId(field, false));
            imgToggle.Src = this.ExpandIntervalImageUrl;
        }

        #endregion

        #region Control IDs.

        // Multiple fields may share the same control. Controls are named after the first field in the share combination.

        private string GetShareName(ISearchEngineField field)
        {
            int shareIdx = this.ControlSharing.GetCombinationIndexForField(field.BindingName);
            return (shareIdx < 0) ? field.BindingName : this.ControlSharing.GetCombination(shareIdx).Fields[0];
        }

        private string GetToggleButtonId(ISearchEngineField field, bool useParentPrefix)
        {
            string fieldBindingName = GetShareName(field);
            if (useParentPrefix)
                return this.ClientID + "_Toggle_" + fieldBindingName + EndValueSuffix;
            else
                return "Toggle_" + fieldBindingName + EndValueSuffix;
        }

        private string GetEndValueCellId(ISearchEngineField field, bool useParentIdPrefix)
        {
            string fieldBindingName = GetShareName(field);
            if (useParentIdPrefix)
                return this.ClientID + "_Cell_" + fieldBindingName + EndValueSuffix;
            else
                return "Cell_" + fieldBindingName + EndValueSuffix;
        }

        private string GetIsValidEndValueCheckBoxId(ISearchEngineField field, bool useParentIdPrefix)
        {
            string fieldBindingName = GetShareName(field);
            if (useParentIdPrefix)
                return this.ClientID + "_IsValid_" + fieldBindingName + EndValueSuffix;
            else
                return "IsValid_" + fieldBindingName + EndValueSuffix;
        }

        private string GetDdlId(ISearchEngineField field, bool isEndValueControl)
        {
            string fieldBindingName = GetShareName(field);
            if (isEndValueControl)
                return "Ddl_" + fieldBindingName + EndValueSuffix;
            else
                return "Ddl_" + fieldBindingName;
        }

        private string GetCalendarId(ISearchEngineField field, bool isEndValueControl)
        {
            string fieldBindingName = GetShareName(field);
            if (isEndValueControl)
                return "Calendar_" + fieldBindingName + EndValueSuffix;
            else
                return "Calendar_" + fieldBindingName;
        }

        private string GetTextBoxId(ISearchEngineField field, bool isEndValueControl)
        {
            string fieldBindingName = GetShareName(field);
            if (isEndValueControl)
                return "Txt_" + fieldBindingName + EndValueSuffix;
            else
                return "Txt_" + fieldBindingName;
        }

        private string GetLookupId(ISearchEngineField field, bool isEndValueControl)
        {
            string fieldBindingName = GetShareName(field);
            if (isEndValueControl)
                return "Look_" + fieldBindingName + EndValueSuffix;
            else
                return "Look_" + fieldBindingName;
        }

        private string GetNegateCheckBoxId(ISearchEngineField field)
        {
            string fieldBindingName = GetShareName(field);
            return "Negate_" + fieldBindingName;
        }

        private string GetAddWithOrCheckBoxId(ISearchEngineField field)
        {
            string fieldBindingName = GetShareName(field);
            return "AddWithOr_" + fieldBindingName;
        }

        private string GetValueSetsDdlId(ISearchEngineField field)
        {
            string fieldBindingName = GetShareName(field);
            return "ValueSet_" + fieldBindingName;
        }

        private string GetAnyWordCheckBoxId(ISearchEngineField field)
        {
            string fieldBindingName = GetShareName(field);
            return "AnyWord_" + fieldBindingName;
        }

        private string GetSelectTextModeDdlId(ISearchEngineField field)
        {
            string fieldBindingName = GetShareName(field);
            return "SelectTextMode_" + fieldBindingName;
        }

        private string GetIsNullCheckBoxId(ISearchEngineField field)
        {
            string fieldBindingName = GetShareName(field);
            return "IsNull_" + fieldBindingName;
        }

        private string GetAdvancedOptionsCellId(ISearchEngineField field)
        {
            string fieldBindingName = GetShareName(field);
            return "AdvOptions_" + fieldBindingName;
        }

        private string GetRowId(ISearchEngineField field)
        {
            string fieldBindingName = GetShareName(field);
            return fieldBindingName + "_Row";
        }

        #endregion

        #region GetControl.

        /// <summary>
        /// Gets the control bound to the specified search field.
        /// </summary>
        /// <param name="field">Search field.</param>
        /// <param name="isEndValueControl">Whether the method should return the control which is bound to <see cref="ISearchEngineField.EndValue"/> property.</param>
        /// <returns>Control; <b>null</b> if no controls are bound to the specified field.</returns>
        public Control GetControl(ISearchEngineField field, bool isEndValueControl)
        {
            Control ctl = null;
            string ctlId = null;

            if (field.LookupBuilder != null)
                ctlId = GetLookupId(field, isEndValueControl);
            else if (field.CodeList != null)
                ctlId = GetDdlId(field, isEndValueControl);
            else if (TypeUtil.IsDate(field.DataType))
                ctlId = GetCalendarId(field, isEndValueControl);
            else
                ctlId = GetTextBoxId(field, isEndValueControl);

            if (ctlId != null)
                ctl = FindControl(ctlId);

            return ctl;
        }

        private CheckBox GetAddWithOrCheckBox(ISearchEngineField field)
        {
            return FindControl(GetAddWithOrCheckBoxId(field)) as CheckBox;
        }

        private CheckBox GetNegateCheckBox(ISearchEngineField field)
        {
            return FindControl(GetNegateCheckBoxId(field)) as CheckBox;
        }

        private DropDownList GetValueSetsDdl(ISearchEngineField field)
        {
            return FindControl(GetValueSetsDdlId(field)) as DropDownList;
        }

        private DropDownList GetSelectTextModeDdl(ISearchEngineField field)
        {
            return FindControl(GetSelectTextModeDdlId(field)) as DropDownList;
        }

        private CheckBox GetIsNullCheckBox(ISearchEngineField field)
        {
            return FindControl(GetIsNullCheckBoxId(field)) as CheckBox;
        }

        #endregion

        #region Get control values.

        private object GetValue(ISearchEngineField field, bool isEndValueControl, out bool useExactTextMatch)
        {
            object val = null;
            useExactTextMatch = false;
            IMessageFormatter formatter = this.Formatter;

            // Extra check for end-value controls. Hidden checkbox indicates whether the value has been entered.
            if (isEndValueControl && HasValidEndValueCheckboxStatus(field) == false)
                return null;

            if (field.LookupBuilder != null)
            {
                string ctlId = GetLookupId(field, isEndValueControl);
                IBoundLookupControl lookup = FindControl(ctlId) as IBoundLookupControl;
                if (lookup != null)
                {
                    if (TypeUtil.IsText(field.DataType))
                    {
                        bool exactMatchAllowed = Array.IndexOf<TextSearchMode>(field.AllowedTextSearchModes, TextSearchMode.ExactMatch) >= 0;
                        bool isSelectedFromLookup = (lookup.SelectedValue.Trim() == lookup.Text.Trim());
                        useExactTextMatch = (exactMatchAllowed && isSelectedFromLookup);
                    }

                    val = ParseValue(field, lookup.Text, formatter);
                }
            }
            else if (field.CodeList != null)
            {
                // Value selected in a dropdown list.
                string ctlId = GetDdlId(field, isEndValueControl);
                DropDownList ddl = FindControl(ctlId) as DropDownList;
                if (ddl != null)
                    val = ParseValue(field, ddl.SelectedValue, formatter);
            }
            else if (TypeUtil.IsDate(field.DataType))
            {
                // Date selected in calendar control.
                string ctld = GetCalendarId(field, isEndValueControl);
                BoundCalendar calendar = FindControl(ctld) as BoundCalendar;
                if (calendar != null)
                    val = calendar.SelectedValue;
            }
            else
            {
                // Value entered in textbox control.
                string ctlId = GetTextBoxId(field, isEndValueControl);
                TextBox txtBox = FindControl(ctlId) as TextBox;
                if (txtBox != null)
                {
                    string userEntry = txtBox.Text.Trim();
                    userEntry = RemoveBannedCharsFromString(userEntry);
                    val = ParseValue(field, userEntry, formatter);
                }
            }

            return val;
        }

        private object[] GetMultipleValues(ISearchEngineField field)
        {
            CheckBox chkAnyWord = FindControl(GetAnyWordCheckBoxId(field)) as CheckBox;
            bool anyWordChecked = (chkAnyWord != null && chkAnyWord.Checked);
            bool allowMultipleValues = anyWordChecked && (field.CodeList == null) && (TypeUtil.IsText(field.DataType) || TypeUtil.IsNumber(field.DataType));
            if (!allowMultipleValues)
                return new object[0];

            // Value entered in textbox control.
            string userEntry = null;
            string ctlId = GetTextBoxId(field, false);
            TextBox txtBox = FindControl(ctlId) as TextBox;
            if (txtBox != null)
            {
                userEntry = txtBox.Text.Trim();
                userEntry = RemoveBannedCharsFromString(userEntry);
            }

            if (string.IsNullOrEmpty(userEntry))
                return new object[0];

            object[] values;
            if (!this.IgnorePhrases && TypeUtil.IsText(field.DataType))
            {
                values = ParsePhrases(userEntry);
            }
            else
            {
                string[] enteredValues = userEntry.Split(WordDelimiter);
                ArrayList parsedValues = new ArrayList();
                foreach (string valToParse in enteredValues)
                {
                    object currVal = ParseValue(field, valToParse, this.Formatter);
                    if (currVal != null)
                        parsedValues.Add(currVal);
                }

                values = ArrayUtil.ToArray<object>(parsedValues);
            }

            if (this.MaxWordCount > 0 && values.Length > this.MaxWordCount)
            {
                object[] croppedValues = new object[this.MaxWordCount];
                for (int idxWord = 0; idxWord < croppedValues.Length; idxWord++)
                    croppedValues[idxWord] = values[idxWord];

                values = croppedValues;
            }

            return values;
        }

        private bool HasValidEndValueCheckboxStatus(ISearchEngineField field)
        {
            string chkId = GetIsValidEndValueCheckBoxId(field, false);
            CheckBox chkIsValid = FindControl(chkId) as CheckBox;

            if (chkIsValid != null)
                return chkIsValid.Checked;
            else
                return false;
        }

        private static object ParseValue(IBindableField field, string s, IMessageFormatter formatter)
        {
            return NumberParser.ParseValue(field.DataType, s, formatter);
        }        

        private object[] ParsePhrases(string userEntry)
        {
            List<string> allSearchValues = new List<string>();
            userEntry = userEntry.Trim() + WordDelimiter;

            StringBuilder currSearchValue = new StringBuilder();
            bool isInPhrase = false;
            foreach (char token in userEntry)
            {
                bool tokenIsEndOfWord = (token == WordDelimiter);
                bool tokenIsPhraseMarker = (token == PhraseMarker);

                bool endCurrSearchValue = false;
                if (isInPhrase)
                {
                    if (tokenIsPhraseMarker)
                        endCurrSearchValue = true;
                }
                else
                {
                    if (tokenIsEndOfWord)
                        endCurrSearchValue = true;
                }

                if (endCurrSearchValue)
                {
                    string finishedSearchValue = currSearchValue.ToString().Trim();
                    if (finishedSearchValue.Length > 0)
                        allSearchValues.Add(finishedSearchValue);

                    isInPhrase = false;
                    currSearchValue = new StringBuilder();
                }
                else
                {
                    if (tokenIsPhraseMarker)
                    {
                        // Only mark the beginning of a new phrase.
                        if (isInPhrase)
                            throw new Exception("Already in phrase. Shouldn't be here.");

                        isInPhrase = true;
                    }
                    else
                    {
                        if (tokenIsEndOfWord && !isInPhrase)
                            throw new Exception("Cannot append space character if not in the phrase. Shouldn't be here.");

                        currSearchValue.Append(token);
                    }
                }
            }

            return ArrayUtil.ToArray<object>(allSearchValues);
        }

        private string RemoveBannedCharsFromString(string dirtyString)
        {
            if (string.IsNullOrEmpty(dirtyString) || string.IsNullOrEmpty(this.BannedChars))
                return dirtyString;

            StringBuilder cleanString = new StringBuilder(dirtyString);
            foreach (char bannedChar in this.BannedChars)
                cleanString.Replace(bannedChar.ToString(), "");

            return cleanString.ToString();
        }

        #endregion

        #region Set control values.

        private void SetControlValue(ISearchEngineField field, bool isEndValueControl, object val)
        {
            if (field.LookupBuilder != null)
            {
                string ctlId = GetLookupId(field, isEndValueControl);
                IBoundLookupControl lookup = FindControl(ctlId) as IBoundLookupControl;
                if (lookup != null)
                {
                    string txtValue = this.Formatter.Format(val);
                    lookup.SelectedValue = txtValue;
                    lookup.Text = txtValue;                        
                }
            }
            else if (field.CodeList != null)
            {
                // Value selected in a dropdown list.
                string ctlId = GetDdlId(field, isEndValueControl);
                DropDownList ddl = FindControl(ctlId) as DropDownList;
                if (ddl != null)
                {
                    string valText = this.Formatter.Format(val);
                    if (string.IsNullOrEmpty(valText))
                    {
                        ddl.SelectedIndex = 0;
                    }
                    else
                    {
                        try { ddl.SelectedValue = valText; }
                        catch { ddl.SelectedIndex = 0; }
                    }
                }
            }
            else if (TypeUtil.IsDate(field.DataType))
            {
                // Date selected in calendar control.
                string ctld = GetCalendarId(field, isEndValueControl);
                BoundCalendar calendar = FindControl(ctld) as BoundCalendar;
                if (calendar != null)
                {
                    try { calendar.SelectedValue = (val != null) ? (DateTime?)val : null; }
                    catch { calendar.SelectedValue = null; }
                }
            }
            else
            {
                // Value entered in textbox control.
                string ctlId = GetTextBoxId(field, isEndValueControl);
                TextBox txtBox = FindControl(ctlId) as TextBox;
                if (txtBox != null)
                    txtBox.Text = this.Formatter.Format(val);
            }
        }

        private void SetControlMultipleValues(ISearchEngineField field)
        {
            object[] values = field.Values;
            bool allowMultipleValues = (field.CodeList == null) && (TypeUtil.IsText(field.DataType) || TypeUtil.IsNumber(field.DataType));
            bool multipleValuesProvided = (values != null && values.Length > 1);
            if (!allowMultipleValues || !multipleValuesProvided)
                return;

            // Value entered in textbox control.
            string ctlId = GetTextBoxId(field, false);
            TextBox txtBox = FindControl(ctlId) as TextBox;
            if (txtBox != null)
            {
                foreach (object val in values)
                {
                    string txtRepresentation = this.Formatter.Format(val);
                    bool isPhrase = (!this.IgnorePhrases && TypeUtil.IsText(field.DataType) && txtRepresentation.IndexOf(WordDelimiter) >= 0);
                    if (isPhrase)
                        txtRepresentation = PhraseMarker + txtRepresentation + PhraseMarker;

                    if (!string.IsNullOrEmpty(txtRepresentation))
                        txtBox.Text += txtRepresentation + WordDelimiter;
                }
            }

            txtBox.Text = txtBox.Text.Trim();

            // Set "any word" indicator.
            CheckBox chkAnyWord = FindControl(GetAnyWordCheckBoxId(field)) as CheckBox;
            if (chkAnyWord != null)
                chkAnyWord.Checked = true;
        }

        #endregion

        #region Ordered fields.

        ArrayList orderedFields;

        [Browsable(false)]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        private IList OrderedFields
        {
            get
            {
                if (this.orderedFields == null)
                {
                    SortedList<double, ISearchEngineField> positionedFields = new SortedList<double, ISearchEngineField>();
                    double indexMultiplier = 1.0 / (double)this.Engine.SearchFields.Count;
                    for (int idxField = 0; idxField < this.Engine.SearchFields.Count; idxField++)
                    {
                        ISearchEngineField field = this.Engine.SearchFields[idxField];
                        double fieldPosition = field.Ordinal + (indexMultiplier * idxField);
                        positionedFields.Add(fieldPosition, field);
                    }

                    this.orderedFields = new ArrayList();
                    foreach (KeyValuePair<double, ISearchEngineField> positionAndField in positionedFields)
                        this.orderedFields.Add(positionAndField.Value);
                }

                return this.orderedFields;
            }
        }

        #endregion

        #region Control properties.

        #region UI independent.

        /// <summary>
        /// Gets or sets the underlying search engine which contains meta-data and accesses the data-source.
        /// </summary>
        [Browsable(false)]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public virtual ISearchEngine Engine
        {
            get { return this.engine; }
            set { this.engine = value; }
        }

        /// <summary>
        /// Gets or sets the <see cref="IMessageFormatter"/> object used to format values.
        /// </summary>
        [Browsable(false)]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public virtual IMessageFormatter Formatter
        {
            get { return this.formatter; }
            set { this.formatter = value; }
        }

        /// <summary>
        /// Gets or sets the value indicating whether users can invert filters created for search fields. 
        /// </summary>
        [Category("Behavior")]
        [DefaultValue(false)]
        [Description("Indicates whether users can invert filters created for search fields.")]
        public virtual bool InvertFilterEnabled
        {
            get { return this.invertFilterEnabled; }
            set { this.invertFilterEnabled = value; }
        }

        /// <summary>
        /// Gets or sets the <b>Text</b> property for generated <b>CheckBox</b> controls which
        /// indicates whether filter created for a search field is inverted.
        /// </summary>
        [Category("Appearance")]
        [Description("Text property for generated CheckBox controls which indicate whether filter created for a search field is inverted.")]
        [DefaultValue(null)]
        public virtual string InvertFilterText
        {
            get { return this.TextSource.InvertFilterText ?? this.invertFilterText; }
            set { this.invertFilterText = value; }
        }

        /// <summary>
        /// Gets or sets the <b>ToolTip</b> property for generated <b>CheckBox</b> controls which
        /// indicates whether filter created for a search field is inverted.
        /// </summary>
        [Category("Appearance")]
        [Description("ToolTip property for generated CheckBox controls which indicate whether filter created for a search field is inverted.")]
        [DefaultValue(null)]
        public string InvertFilterToolTip
        {
            get { return this.TextSource.InvertFilterToolTip ?? this.invertFilterToolTip; }
            set { this.invertFilterToolTip = value; }
        }

        /// <summary>
        /// Gets or sets the value indicating whether users can choose 
        /// that filters created for search fields are combined using logical ORs.
        /// </summary>
        [Category("Behavior")]
        [DefaultValue(false)]
        [Description("Indicates whether users can choose that filters created for search fields are combined using logical ORs.")]
        public virtual bool AddWithOrEnabled
        {
            get { return this.addWithOrEnabled; }
            set { this.addWithOrEnabled = value; }
        }

        /// <summary>
        /// Gets or sets the <b>Text</b> property for generated <b>CheckBox</b> controls which 
        /// indicates whether filter created for a search field is combined using logical OR.
        /// </summary>
        [Category("Appearance")]
        [Description("Text property for generated CheckBox controls which indicate whether filter created for a search field is combined using logical OR.")]
        [DefaultValue(null)]
        public virtual string AddWithOrText
        {
            get { return this.TextSource.AddWithOrText ?? this.addWithOrText; }
            set { this.addWithOrText = value; }
        }

        /// <summary>
        /// Gets or sets the <b>ToolTip</b> property for generated <b>CheckBox</b> controls which 
        /// indicates whether filter created for a search field is combined using logical OR.
        /// </summary>
        [Category("Appearance")]
        [Description("ToolTip property for generated CheckBox controls which indicate whether filter created for a search field is combined using logical OR.")]
        [DefaultValue(null)]
        public string AddWithOrToolTip
        {
            get { return this.TextSource.AddWithOrToolTip ?? this.addWithOrToolTip; }
            set { this.addWithOrToolTip = value; }
        }

        /// <summary>
        /// Gets or sets whether users can choose that records which match any of the words or phrases entered 
        /// in a search field are retrieved from the data-source. Ie. whether the entered text is splitted and 
        /// assigned to <see cref="ISearchEngineField.Values"/> property.
        /// </summary>
        /// <value><b>true</b> if this option is enabled; <b>false</b> otherwise. Default is <b>false</b>.</value>
        [Category("Behavior")]
        [DefaultValue(false)]
        [Description("Whether users can choose that records which match any of the words or phrases entered in a search field are retrieved from the data-source.")]
        public virtual bool AnyWordOrPhraseEnabled
        {
            get { return this.anyWordOrPhraseEnabled; }
            set { this.anyWordOrPhraseEnabled = value; }
        }

        /// <summary>
        /// Gets or sets whether users can combine words in phrases when in "find any word" mode.
        /// </summary>
        /// <value><b>true</b> if phrases marked with <see cref="SearchFilter.PhraseMarker"/> character
        /// should be ignored; <b>false</b> otherwise. Default is <b>false</b>.</value>
        /// <remarks>Has no effect if <see cref="AnyWordOrPhraseEnabled"/> property is set to <b>false</b>.</remarks>
        [Category("Behavior")]
        [DefaultValue(false)]
        [Description("Whether users can combine words in phrases when in \"find any word\" mode.")]
        public virtual bool IgnorePhrases
        {
            get { return this.ignorePhrases; }
            set { this.ignorePhrases = value; }
        }

        /// <summary>
        /// Gets or sets the <b>Text</b> property for generated <b>CheckBox</b> controls which indicates whether 
        /// filter created for a search field will try to find any of the specified words/phrases.
        /// </summary>
        [Category("Appearance")]
        [Description("Text property for generated CheckBox controls which indicate whether filter created for a search field will try to find any of the specified words/phrases.")]
        [DefaultValue(null)]
        public virtual string AnyWordOrPhraseText
        {
            get { return this.TextSource.AnyWordOrPhraseText ?? this.anyWordOrPhraseText; }
            set { this.anyWordOrPhraseText = value; }
        }

        /// <summary>
        /// Gets or sets the <b>ToolTip</b> property for generated <b>CheckBox</b> controls which indicates whether 
        /// filter created for a search field will try to find any of the specified words/phrases.
        /// </summary>
        [Category("Appearance")]
        [Description("ToolTip property for generated CheckBox controls which indicate whether filter created for a search field will try to find any of the specified words/phrases.")]
        [DefaultValue(null)]
        public string AnyWordOrPhraseToolTip
        {
            get { return this.TextSource.AnyWordOrPhraseToolTip ?? this.anyWordOrPhraseToolTip; }
            set { this.anyWordOrPhraseToolTip = value; }
        }

        /// <summary>
        /// Gets or sets a list of characters that users cannot enter.
        /// </summary>
        /// <remarks><para>A client-side script disables all characters specified in this property. Additionally, if
        /// users somehow bypass the client-side script, all values submitted to server are cleaned.</para>
        /// <para>It might be a good idea to add a percentage (<b>%</b>) character to the list to prohibit 
        /// users to use the SQL wildcard symbol.</para></remarks>
        [Category("Behavior")]
        [Description("A list of characters that users cannot enter.")]
        [DefaultValue(null)]
        public virtual string BannedChars
        {
            get { return this.bannedChars; }
            set { this.bannedChars = value; }
        }

        /// <summary>
        /// Gets or sets the maximum number of values/words/phrases a user can search for.
        /// </summary>
        /// <value>Number of values/words the user can search for. Unlimitted if zero or a negative number.</value>
        /// <remarks><para>Has no effect if <see cref="AnyWordOrPhraseEnabled"/> is set to <b>false</b>.</para>
        /// <para>The excess values are ignored.</para></remarks>
        [Category("Behavior")]
        [DefaultValue(0)]
        [Description("Maximum number of values/words/phrases a user can search for.")]
        public virtual int MaxWordCount
        {
            get { return this.maxWordCount; }
            set { this.maxWordCount = value; }
        }

        /// <summary>
        /// Gets or sets a named collection of serialized filter states (see <see cref="SearchEngineFieldsState"/>) 
        /// compatibile with the provided <see cref="Engine"/>.
        /// </summary>
        [Browsable(false)]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public virtual IDictionary<string, string> PredefinedSearchEngineFieldsStates
        {
            get { return this.predefinedSearchEngineFieldsStates; }
            set { this.predefinedSearchEngineFieldsStates = (value != null) ? value : new Dictionary<string, string>(); }
        }

        /// <summary>
        /// Gets or sets whether the search fields whose <see cref="ISearchEngineField.IsAutoPopulated"/> 
        /// property is set to <b>true</b> may be overwritten in <see cref="Search()"/> method. Default is <b>false</b>.
        /// </summary>
        [DefaultValue(false)]
        [Description("Indicates whether the search fields whose IsAutoPopulated property is set to true may be overwritten.")]
        [Category("Behavior")]
        public virtual bool OverwriteAutoPopulatedFields
        {
            get { return this.overwriteAutoPopulatedFields; }
            set { this.overwriteAutoPopulatedFields = value; }
        }

        /// <summary>
        /// Gets or sets whether the search fields whose <see cref="IBindableField.IsHidden"/> 
        /// property is set to <b>true</b> may be overwritten in <see cref="Search()"/> method. Default is <b>false</b>.
        /// </summary>
        [DefaultValue(false)]
        [Description("Indicates whether the search fields whose IsHidden property is set to true may be overwritten.")]
        [Category("Behavior")]
        public virtual bool OverwriteHiddenFields
        {
            get { return this.overwriteHiddenFields; }
            set { this.overwriteHiddenFields = value; }
        }

        /// <summary>
        /// Gets or sets whether the search fields whose <see cref="ISearchEngineField.IsAutoPopulated"/> 
        /// property is set to <b>true</b> may be reset by <see cref="Reset()"/> method. Default is <b>false</b>.
        /// </summary>
        [DefaultValue(false)]
        [Description("Indicates whether the search fields whose IsAutoPopulated property is set to true may be reset.")]
        [Category("Behavior")]
        public virtual bool ResetAutoPopulatedFields
        {
            get { return this.resetAutoPopulatedFields; }
            set { this.resetAutoPopulatedFields = value; }
        }

        /// <summary>
        /// Gets or sets whether the search fields whose <see cref="IBindableField.IsHidden"/> 
        /// property is set to <b>true</b> may be reset by <see cref="Reset()"/> method. Default is <b>false</b>.
        /// </summary>
        [DefaultValue(false)]
        [Description("Indicates whether the search fields whose IsHidden property is set to true may be reset.")]
        [Category("Behavior")]
        public virtual bool ResetHiddenFields
        {
            get { return this.resetHiddenFields; }
            set { this.resetHiddenFields = value; }
        }

        /// <summary>
        /// Gets or sets whether users can choose how textual fields are searched.
        /// </summary>
        /// <value><b>true</b> if this option is enabled; <b>false</b> otherwise. Default is <b>false</b>.</value>
        [Category("Behavior")]
        [DefaultValue(false)]
        [Description("Whether users can choose how textual fields are searched.")]
        public bool TextSearchModeSelectionEnabled
        {
            get { return textSearchModeSelectionEnabled; }
            set { textSearchModeSelectionEnabled = value; }
        }

        /// <summary>
        /// Gets or sets the caption for the <see cref="TextSearchMode.Default"/> option.
        /// </summary>
        [Category("Appearance")]
        [Description("Caption for the TextSearchMode.Default option.")]
        [DefaultValue(null)]
        public string TextSearchModeDefaultCaption
        {
            get { return this.TextSource.TextSearchModeDefaultCaption ?? this.textSearchModeDefaultCaption; }
            set { this.textSearchModeDefaultCaption = value; }
        }

        /// <summary>
        /// Gets or sets the caption for the <see cref="TextSearchMode.AnywhereInText"/> option.
        /// </summary>
        [Category("Appearance")]
        [Description("Caption for the TextSearchMode.AnywhereInText option.")]
        [DefaultValue(null)]
        public string TextSearchModeAnywhereInTextCaption
        {
            get { return this.TextSource.TextSearchModeAnywhereInTextCaption ?? this.textSearchModeAnywhereInTextCaption; }
            set { this.textSearchModeAnywhereInTextCaption = value; }
        }

        /// <summary>
        /// Gets or sets the caption for the <see cref="TextSearchMode.BeginningOfText"/> option.
        /// </summary>
        [Category("Appearance")]
        [Description("Caption for the TextSearchMode.BeginningOfText option.")]
        [DefaultValue(null)]
        public string TextSearchModeBeginningOfTextCaption
        {
            get { return this.TextSource.TextSearchModeBeginningOfTextCaption ?? this.textSearchModeBeginningOfTextCaption; }
            set { this.textSearchModeBeginningOfTextCaption = value; }
        }

        /// <summary>
        /// Gets or sets the caption for the <see cref="TextSearchMode.ExactMatch"/> option.
        /// </summary>
        [Category("Appearance")]
        [Description("Caption for the TextSearchMode.ExactMatch option.")]
        [DefaultValue(null)]
        public string TextSearchModeExactMatchCaption
        {
            get { return this.TextSource.TextSearchModeExactMatchCaption ?? this.textSearchModeExactMatchCaption; }
            set { this.textSearchModeExactMatchCaption = value; }
        }

        /// <summary>
        /// Gets or sets the value indicating whether users can choose to look for records where the field value is not set, ie. null.
        /// </summary>
        [Category("Behavior")]
        [DefaultValue(false)]
        [Description("Indicates whether whether users can choose to look for records where the field value is not set, ie. null.")]
        public bool IsNullEnabled
        {
            get { return this.isNullEnabled; }
            set { this.isNullEnabled = value; }
        }

        /// <summary>
        /// Gets or sets the <b>Text</b> property for generated <b>CheckBox</b> controls which
        /// indicates whether users can choose to look for records where the field value is not set, ie. null.
        /// </summary>
        [Category("Appearance")]
        [Description("Text property for generated CheckBox controls which indicate whether users can choose to look for records where the field value is not set, ie. null.")]
        [DefaultValue(null)]
        public string IsNullText
        {
            get { return this.TextSource.IsNullText ?? this.isNullText; }
            set { this.isNullText = value; }
        }

        /// <summary>
        /// Gets or sets the <b>ToolTip</b> property for generated <b>CheckBox</b> controls which
        /// indicates whether users can choose to look for records where the field value is not set, ie. null.
        /// </summary>
        [Category("Appearance")]
        [Description("ToolTip property for generated CheckBox controls which indicate whether users can choose to look for records where the field value is not set, ie. null.")]
        [DefaultValue(null)]
        public string IsNullToolTip
        {
            get { return this.TextSource.IsNullToolTip ?? this.isNullToolTip; }
            set { this.isNullToolTip = value; }
        }

        /// <summary>
        /// Gets or sets the caption of <b>show advanced search options</b> toggle button.
        /// </summary>
        [Category("Appearance")]
        [Description("Show advanced search options toggle button caption.")]
        [DefaultValue(null)]
        public string ShowAdvancedOptionsCaption
        {
            get { return this.TextSource.ShowAdvancedOptionsCaption ?? this.showAdvancedOptionsCaption; }
            set { this.showAdvancedOptionsCaption = value; }
        }

        /// <summary>
        /// Gets or sets the caption of <b>hide advanced search options</b> toggle button.
        /// </summary>
        [Category("Appearance")]
        [Description("Hide advanced search options toggle button caption.")]
        [DefaultValue(null)]
        public string HideAdvancedOptionsCaption
        {
            get { return this.TextSource.HideAdvancedOptionsCaption ?? this.hideAdvancedOptionsCaption; }
            set { this.hideAdvancedOptionsCaption = value; }
        }

        /// <summary>
        /// Gets or sets whether the advanced search options are initially visible.
        /// </summary>
        [Category("Appearance")]
        [Description("Indicates whether the advanced search options are initially visible.")]
        [DefaultValue(true)]
        public bool AdvancedOptionsVisible
        {
            get { return this.advancedOptionsVisible; }
            set { this.advancedOptionsVisible = value; }
        }

        /// <summary>
        /// Gets or sets whether the buttons which toggles advanced options is visible.
        /// </summary>
        [Category("Appearance")]
        [Description("Indicates whether the buttons which toggles advanced options is visible.")]
        [DefaultValue(false)]
        public bool ToggleAdvancedOptionsButtonVisible
        {
            get { return this.toggleAdvancedOptionsButtonVisible; }
            set { this.toggleAdvancedOptionsButtonVisible = value; }
        }

        #endregion

        // Web dependenent.

        /// <summary>
        /// Gets or sets the Cascading Style Sheet (CSS) class rendered by the Web server control on the client. 
        /// </summary>
        [Category("Appearance")]
        [Description("(SS class rendered by the Web server control on the client.")]
        public override string CssClass
        {
            get { return this.tblControlContainer.CssClass; }
            set { this.tblControlContainer.CssClass = value; }
        }

        #region Search button.

        /// <summary>
        /// Gets or sets the <b>search button</b> type.
        /// </summary>
        /// <value>Button type. Default is <see cref="ButtonType.Button"/>.</value>
        /// <remarks>Currently, only <see cref="ButtonType.Button"/> and <see cref="ButtonType.TemplateButton"/>
        /// are supported. If an unsupported type is specified, the <see cref="ButtonType.Button"/> is used.</remarks>
        [Category("Appearance")]
        [DefaultValue(typeof(ButtonType), "Button")]
        [Description("Search button type. Only Button and TemplateButton are supported.")]
        public virtual ButtonType SearchButtonType
        {
            get { return this.searchButtonType; }
            set { this.searchButtonType = value; }
        }

        /// <summary>
        /// Gets or sets the text caption displayed in the <b>search button</b> control.
        /// </summary>
        /// <remarks>The search button populates the engine filter and raises the <see cref="FilterCommand"/> event
        /// with <see cref="SearchFilterCommands.FilterStateChanged"/> and <see cref="SearchFilterCommands.Search"/> flags.</remarks>
        [Category("Appearance")]
        [Description("Text caption displayed in the search button control.")]
        [DefaultValue("")]
        public virtual string SearchButtonText
        {
            get { return this.btnSearch.Text; }
            set 
            { 
                this.btnSearch.Text = value;
                this.btnSearchCustom.Text = value;
            }
        }

        /// <summary>
        /// Gets or sets the width of the <b>search button</b> control.
        /// </summary>
        /// <remarks>The search button populates the engine filter and raises the <see cref="FilterCommand"/> event
        /// with <see cref="SearchFilterCommands.FilterStateChanged"/> and <see cref="SearchFilterCommands.Search"/> flags.</remarks>
        [Category("Appearance")]
        [Description("Width of the search button control.")]
        [DefaultValue(typeof(Unit), "")]
        public virtual Unit SearchButtonWidth
        {
            get { return this.btnSearch.Width; }
            set 
            { 
                this.btnSearch.Width = value;
                this.btnSearchCustom.Width = value;
            }
        }

        /// <summary>
        /// Gets or sets the Cascading Style Sheet (CSS) class applied to the <b>search button</b> control.
        /// </summary>
        /// <remarks>The search button populates the engine filter and raises the <see cref="FilterCommand"/> event
        /// with <see cref="SearchFilterCommands.FilterStateChanged"/> and <see cref="SearchFilterCommands.Search"/> flags.</remarks>
        [Category("Appearance")]
        [Description("CSS class applied to the search button control.")]
        [DefaultValue("")]
        public virtual string SearchButtonCssClass
        {
            get { return this.btnSearch.CssClass; }
            set 
            { 
                this.btnSearch.CssClass = value;
                this.btnSearchCustom.CssClass = value;
            }
        }

        /// <summary>
        /// Gets or sets a value that indicates whether the <b>search button</b> control is rendered on the page. 
        /// </summary>
        /// <remarks>The search button populates the engine filter and raises the <see cref="FilterCommand"/> event
        /// with <see cref="SearchFilterCommands.FilterStateChanged"/> and <see cref="SearchFilterCommands.Search"/> flags.</remarks>
        [Category("Appearance")]
        [DefaultValue(typeof(bool), "true")]
        [Description("Indicates whether the search button control is rendered on the page.")]
        public virtual bool SearchButtonVisible
        {
            get { return this.searchButtonVisible; }
            set { this.searchButtonVisible = value; }
        }

        /// <summary>
        /// Gets or sets client-side script executed when the <b>search button</b> is clicked.
        /// </summary>
        /// <remarks>The search button populates the engine filter and raises the <see cref="FilterCommand"/> event
        /// with <see cref="SearchFilterCommands.FilterStateChanged"/> and <see cref="SearchFilterCommands.Search"/> flags.</remarks>
        [Category("Behavior")]
        [Description("Client-side script executed when the search button is clicked.")]
        [DefaultValue(null)]
        public virtual string SearchButtonScript
        {
            get { return this.btnSearch.Attributes["onclick"]; }
            set 
            { 
                this.btnSearch.Attributes["onclick"] = value;
                this.btnSearchCustom.Attributes["onclick"] = value;
            }
        }

        /// <summary>
        /// Gets or sets <b>search button</b> template.
        /// </summary>
        /// <remarks>No effect if <see cref="SearchButtonType"/> is not se to <see cref="ButtonType.TemplateButton"/>.</remarks>
        [DefaultValue(null)]
        [Description("HTML template/format string for search button.")]
        [Category("Behavior")]
        public virtual string SearchButtonTemplate
        {
            get { return this.btnSearchCustom.Template; }
            set { this.btnSearchCustom.Template = value; }
        }

        /// <summary>
        /// Gets or sets semicolon delimitted <see cref="TemplateButton"/> property names whose values are formatted as defined by <see cref="SearchButtonTemplate"/>.
        /// </summary>
        /// <remarks>No effect if <see cref="SearchButtonType"/> is not se to <see cref="ButtonType.TemplateButton"/>.</remarks>
        [DefaultValue("")]
        [Description("Semicolon delimitted TemplateButton properties which are formatted as defined by SearchButtonTemplate.")]
        [Category("Behavior")]
        public virtual string SearchButtonTemplateArguments
        {
            get { return this.btnSearchCustom.TemplateArguments; }
            set { this.btnSearchCustom.TemplateArguments = value; }
        }

        /// <summary>
        /// Gets or sets optional client side scripts which are used in <see cref="SearchButtonTemplate"/>.
        /// </summary>
        /// <remarks>No effect if <see cref="SearchButtonType"/> is not set to <see cref="ButtonType.TemplateButton"/>.</remarks>
        [DefaultValue(null)]
        [Description("Client side scripts which are used in SearchButtonTemplate.")]
        [Category("Behavior")]
        public virtual string SearchButtonTemplateScript
        {
            get { return this.btnSearchCustom.ClientScriptBlock; }
            set { this.btnSearchCustom.ClientScriptBlock = value; }
        }

        /// <summary>
        /// Gets or sets semicolon delimitted JavaScript URLs/files which should be included in the page.
        /// </summary>
        /// <remarks>No effect if <see cref="SearchButtonType"/> is not se to <see cref="ButtonType.TemplateButton"/>.</remarks>
        [DefaultValue("")]
        [Description("Semicolon delimitted JavaScript URLs/files which should be included in the page.")]
        [Category("Behavior")]
        public virtual string SearchButtonTemplateScriptIncludeUrls
        {
            get { return this.btnSearchCustom.ClientScriptIncludeUrls; }
            set { this.btnSearchCustom.ClientScriptIncludeUrls = value; }
        }

        #endregion

        #region Reset button.

        /// <summary>
        /// Gets or sets the <b>reset button</b> type.
        /// </summary>
        /// <value>Button type. Default is <see cref="ButtonType.Button"/>.</value>
        /// <remarks>Currently, only <see cref="ButtonType.Button"/> and <see cref="ButtonType.TemplateButton"/>
        /// are supported. If an unsupported type is specified, the <see cref="ButtonType.Button"/> is used.</remarks>
        [Category("Appearance")]
        [DefaultValue(typeof(ButtonType), "Button")]
        [Description("Reset button type. Only Button and TemplateButton are supported.")]
        public virtual ButtonType ResetButtonType
        {
            get { return this.resetButtonType; }
            set { this.resetButtonType = value; }
        }

        /// <summary>
        /// Gets or sets the text caption displayed in the <b>reset button</b> control.
        /// </summary>
        /// <remarks>The reset button resets the engine filter and raises the <see cref="FilterCommand"/> event
        /// with <see cref="SearchFilterCommands.FilterStateChanged"/>, <see cref="SearchFilterCommands.Search"/>
        /// and <see cref="SearchFilterCommands.Reset"/> flags.</remarks>
        [Category("Appearance")]
        [Description("Text caption displayed in the reset button control.")]
        [DefaultValue("")]
        public virtual string ResetButtonText
        {
            get { return this.btnReset.Text; }
            set 
            { 
                this.btnReset.Text = value;
                this.btnResetCustom.Text = value;
            }
        }

        /// <summary>
        /// Gets or sets the width of the <b>reset button</b> control.
        /// </summary>
        /// <remarks>The reset button resets the engine filter and raises the <see cref="FilterCommand"/> event
        /// with <see cref="SearchFilterCommands.FilterStateChanged"/>, <see cref="SearchFilterCommands.Search"/>
        /// and <see cref="SearchFilterCommands.Reset"/> flags.</remarks>
        [Category("Appearance")]
        [Description("Width of the reset button control.")]        
        [DefaultValue(typeof(Unit), "")]
        public virtual Unit ResetButtonWidth
        {
            get { return this.btnReset.Width; }
            set 
            { 
                this.btnReset.Width = value;
                this.btnResetCustom.Width = value;
            }
        }

        /// <summary>
        /// Gets or sets the Cascading Style Sheet (CSS) class applied to the <b>reset button</b> control.
        /// </summary>
        /// <remarks>The reset button resets the engine filter and raises the <see cref="FilterCommand"/> event
        /// with <see cref="SearchFilterCommands.FilterStateChanged"/>, <see cref="SearchFilterCommands.Search"/>
        /// and <see cref="SearchFilterCommands.Reset"/> flags.</remarks>
        [Category("Appearance")]
        [Description("CSS class applied to the reset button control.")]
        [DefaultValue("")]
        public virtual string ResetButtonCssClass
        {
            get { return this.btnReset.CssClass; }
            set 
            { 
                this.btnReset.CssClass = value;
                this.btnResetCustom.CssClass = value;
            }
        }

        /// <summary>
        /// Gets or sets a value that indicates whether the <b>reset button</b> control is rendered on the page. 
        /// </summary>
        /// <remarks>The reset button resets the engine filter and raises the <see cref="FilterCommand"/> event
        /// with <see cref="SearchFilterCommands.FilterStateChanged"/>, <see cref="SearchFilterCommands.Search"/>
        /// and <see cref="SearchFilterCommands.Reset"/> flags.</remarks>
        [Category("Appearance")]
        [DefaultValue(true)]
        [Description("Indicates whether the reset button control is rendered on the page.")]
        public virtual bool ResetButtonVisible
        {
            get { return this.resetButtonVisible; }
            set { this.resetButtonVisible = value; }
        }

        /// <summary>
        /// Gets or sets client-side script executed when the <b>reset button</b> is clicked.
        /// </summary>
        /// <remarks>The reset button resets the engine filter and raises the <see cref="FilterCommand"/> event
        /// with <see cref="SearchFilterCommands.FilterStateChanged"/>, <see cref="SearchFilterCommands.Search"/>
        /// and <see cref="SearchFilterCommands.Reset"/> flags.</remarks>
        [Category("Behavior")]
        [Description("Client-side script executed when the reset button is clicked.")]
        [DefaultValue(null)]
        public virtual string ResetButtonScript
        {
            get { return this.btnReset.Attributes["onclick"]; }
            set 
            { 
                this.btnReset.Attributes["onclick"] = value;
                this.btnResetCustom.Attributes["onclick"] = value;
            }
        }

        /// <summary>
        /// Gets or sets <b>reset button</b> template.
        /// </summary>
        /// <remarks>No effect if <see cref="ResetButtonType"/> is not se to <see cref="ButtonType.TemplateButton"/>.</remarks>
        [DefaultValue(null)]
        [Description("HTML template/format string for reset button.")]
        [Category("Behavior")]
        public virtual string ResetButtonTemplate
        {
            get { return this.btnResetCustom.Template; }
            set { this.btnResetCustom.Template = value; }
        }

        /// <summary>
        /// Gets or sets semicolon delimitted <see cref="TemplateButton"/> property names whose values are formatted as defined by <see cref="ResetButtonTemplate"/>.
        /// </summary>
        /// <remarks>No effect if <see cref="ResetButtonType"/> is not se to <see cref="ButtonType.TemplateButton"/>.</remarks>
        [DefaultValue("")]
        [Description("Semicolon delimitted TemplateButton properties which are formatted as defined by ResetButtonTemplate.")]
        [Category("Behavior")]
        public virtual string ResetButtonTemplateArguments
        {
            get { return this.btnResetCustom.TemplateArguments; }
            set { this.btnResetCustom.TemplateArguments = value; }
        }

        /// <summary>
        /// Gets or sets optional client side scripts which are used in <see cref="ResetButtonTemplate"/>.
        /// </summary>
        /// <remarks>No effect if <see cref="ResetButtonType"/> is not set to <see cref="ButtonType.TemplateButton"/>.</remarks>
        [DefaultValue(null)]
        [Description("Client side scripts which are used in ResetButtonTemplate.")]
        [Category("Behavior")]
        public virtual string ResetButtonTemplateScript
        {
            get { return this.btnResetCustom.ClientScriptBlock; }
            set { this.btnResetCustom.ClientScriptBlock = value; }
        }

        /// <summary>
        /// Gets or sets semicolon delimitted JavaScript URLs/files which should be included in the page.
        /// </summary>
        /// <remarks>No effect if <see cref="ResetButtonType"/> is not se to <see cref="ButtonType.TemplateButton"/>.</remarks>
        [DefaultValue("")]
        [Description("Semicolon delimitted JavaScript URLs/files which should be included in the page.")]
        [Category("Behavior")]
        public virtual string ResetButtonTemplateScriptIncludeUrls
        {
            get { return this.btnResetCustom.ClientScriptIncludeUrls; }
            set { this.btnResetCustom.ClientScriptIncludeUrls = value; }
        }        

        #endregion

        #region Save button.

        /// <summary>
        /// Gets or sets the <b>save button</b> type.
        /// </summary>
        /// <value>Button type. Default is <see cref="ButtonType.Button"/>.</value>
        /// <remarks>Currently, only <see cref="ButtonType.Button"/> and <see cref="ButtonType.TemplateButton"/>
        /// are supported. If an unsupported type is specified, the <see cref="ButtonType.Button"/> is used.</remarks>
        [Category("Appearance")]
        [DefaultValue(typeof(ButtonType), "Button")]
        [Description("Save button type. Only Button and TemplateButton are supported.")]
        public virtual ButtonType SaveButtonType
        {
            get { return this.saveFilterForActorButtonType; }
            set { this.saveFilterForActorButtonType = value; }
        }

        /// <summary>
        /// Gets or sets the text caption displayed in the <b>save button</b> control.
        /// </summary>
        /// <remarks>The save button populates the engine filter and raises the <see cref="FilterCommand"/> event 
        /// with <see cref="SearchFilterCommands.FilterStateChanged"/>, <see cref="SearchFilterCommands.Search"/>
        /// and <see cref="SearchFilterCommands.SaveFilterForActor"/> flags.</remarks>
        [Category("Appearance")]
        [Description("Text caption displayed in the save button control.")]
        [DefaultValue("")]
        public virtual string SaveButtonText
        {
            get { return this.btnSaveFilterForActor.Text; }
            set 
            { 
                this.btnSaveFilterForActor.Text = value;
                this.btnSaveCustom.Text = value;
            }
        }

        /// <summary>
        /// Gets or sets the width of the <b>save button</b> control.
        /// </summary>
        /// <remarks>The save button populates the engine filter and raises the <see cref="FilterCommand"/> event 
        /// with <see cref="SearchFilterCommands.FilterStateChanged"/>, <see cref="SearchFilterCommands.Search"/>
        /// and <see cref="SearchFilterCommands.SaveFilterForActor"/> flags.</remarks>
        [Category("Appearance")]
        [Description("Width if the save button control.")]
        [DefaultValue(typeof(Unit), "")]
        public virtual Unit SaveButtonWidth
        {
            get { return this.btnSaveFilterForActor.Width; }
            set 
            { 
                this.btnSaveFilterForActor.Width = value;
                this.btnSaveCustom.Width = value;
            }
        }

        /// <summary>
        /// Gets or sets the Cascading Style Sheet (CSS) class applied to the <b>save button</b> control.
        /// </summary>
        /// <remarks>The save button populates the engine filter and raises the <see cref="FilterCommand"/> event 
        /// with <see cref="SearchFilterCommands.FilterStateChanged"/>, <see cref="SearchFilterCommands.Search"/>
        /// and <see cref="SearchFilterCommands.SaveFilterForActor"/> flags.</remarks>
        [Category("Appearance")]
        [Description("CSS class applied to the save button control.")]
        [DefaultValue("")]
        public virtual string SaveButtonCssClass
        {
            get { return this.btnSaveFilterForActor.CssClass; }
            set 
            { 
                this.btnSaveFilterForActor.CssClass = value;
                this.btnSaveCustom.CssClass = value;
            }
        }

        /// <summary>
        /// Gets or sets a value that indicates whether the <b>save button</b> control is rendered on the page. 
        /// </summary>
        /// <remarks>The save button populates the engine filter and raises the <see cref="FilterCommand"/> event 
        /// with <see cref="SearchFilterCommands.FilterStateChanged"/>, <see cref="SearchFilterCommands.Search"/>
        /// and <see cref="SearchFilterCommands.SaveFilterForActor"/> flags.</remarks>
        [Category("Appearance")]
        [DefaultValue(false)]
        [Description("Indicates whether the save button control is rendered on the page.")]
        public virtual bool SaveButtonVisible
        {
            get { return this.saveFilterForActorButtonVisible; }
            set { this.saveFilterForActorButtonVisible = value; }
        }

        /// <summary>
        /// Gets or sets client-side script executed when the <b>save button</b> is clicked.
        /// </summary>
        /// <remarks>The save button populates the engine filter and raises the <see cref="FilterCommand"/> event 
        /// with <see cref="SearchFilterCommands.FilterStateChanged"/>, <see cref="SearchFilterCommands.Search"/>
        /// and <see cref="SearchFilterCommands.SaveFilterForActor"/> flags.</remarks>
        [Category("Behavior")]
        [Description("Client-side script executed when the save button is clicked.")]
        [DefaultValue(null)]
        public virtual string SaveButtonScript
        {
            get { return this.btnSaveFilterForActor.Attributes["onclick"]; }
            set 
            { 
                this.btnSaveFilterForActor.Attributes["onclick"] = value;
                this.btnSaveCustom.Attributes["onclick"] = value;
            }
        }

        /// <summary>
        /// Gets or sets <b>save button</b> template.
        /// </summary>
        /// <remarks>No effect if <see cref="SaveButtonType"/> is not se to <see cref="ButtonType.TemplateButton"/>.</remarks>
        [DefaultValue(null)]
        [Description("HTML template/format string for save button.")]
        [Category("Behavior")]
        public virtual string SaveButtonTemplate
        {
            get { return this.btnSaveCustom.Template; }
            set { this.btnSaveCustom.Template = value; }
        }

        /// <summary>
        /// Gets or sets semicolon delimitted <see cref="TemplateButton"/> property names whose values are formatted as defined by <see cref="SaveButtonTemplate"/>.
        /// </summary>
        /// <remarks>No effect if <see cref="SaveButtonType"/> is not se to <see cref="ButtonType.TemplateButton"/>.</remarks>
        [DefaultValue("")]
        [Description("Semicolon delimitted TemplateButton properties which are formatted as defined by SaveButtonTemplate.")]
        [Category("Behavior")]
        public virtual string SaveButtonTemplateArguments
        {
            get { return this.btnSaveCustom.TemplateArguments; }
            set { this.btnSaveCustom.TemplateArguments = value; }
        }

        /// <summary>
        /// Gets or sets optional client side scripts which are used in <see cref="SaveButtonTemplate"/>.
        /// </summary>
        /// <remarks>No effect if <see cref="SaveButtonType"/> is not set to <see cref="ButtonType.TemplateButton"/>.</remarks>
        [DefaultValue(null)]
        [Description("Client side scripts which are used in SaveButtonTemplate.")]
        [Category("Behavior")]
        public virtual string SaveButtonTemplateScript
        {
            get { return this.btnSaveCustom.ClientScriptBlock; }
            set { this.btnSaveCustom.ClientScriptBlock = value; }
        }

        /// <summary>
        /// Gets or sets semicolon delimitted JavaScript URLs/files which should be included in the page.
        /// </summary>
        /// <remarks>No effect if <see cref="SaveButtonType"/> is not se to <see cref="ButtonType.TemplateButton"/>.</remarks>
        [DefaultValue("")]
        [Description("Semicolon delimitted JavaScript URLs/files which should be included in the page.")]
        [Category("Behavior")]
        public virtual string SaveButtonTemplateScriptIncludeUrls
        {
            get { return this.btnSaveCustom.ClientScriptIncludeUrls; }
            set { this.btnSaveCustom.ClientScriptIncludeUrls = value; }
        }

        #endregion        

        #region Find filter button.

        /// <summary>
        /// Gets or sets the <b>find filter button</b> type.
        /// </summary>
        /// <value>Button type. Default is <see cref="ButtonType.Button"/>.</value>
        /// <remarks>Currently, only <see cref="ButtonType.Button"/> and <see cref="ButtonType.TemplateButton"/>
        /// are supported. If an unsupported type is specified, the <see cref="ButtonType.Button"/> is used.</remarks>
        [Category("Appearance")]
        [DefaultValue(typeof(ButtonType), "Button")]
        [Description("Find filter button type. Only Button and TemplateButton are supported.")]
        public virtual ButtonType FindFilterButtonType
        {
            get { return this.findFilterForActorButtonType; }
            set { this.findFilterForActorButtonType = value; }
        }

        /// <summary>
        /// Gets or sets the text caption displayed in the <b>Find filter button</b> control.
        /// </summary>
        /// <remarks>The save button populates the engine filter and raises the raises the <see cref="FilterCommand"/> event
        /// with <see cref="SearchFilterCommands.FilterStateChanged"/>, <see cref="SearchFilterCommands.Search"/>
        /// and <see cref="SearchFilterCommands.FindOrManageFiltersForActor"/> flags.</remarks>
        [Category("Appearance")]
        [Description("Text caption displayed in the Find filter button control.")]
        [DefaultValue("")]
        public virtual string FindFilterButtonText
        {
            get { return this.btnFindFilterForActor.Text; }
            set 
            { 
                this.btnFindFilterForActor.Text = value;
                this.btnFindFilterForActorCustom.Text = value;
            }
        }

        /// <summary>
        /// Gets or sets the width of the <b>find filter button</b> control.
        /// </summary>
        /// <remarks>The save button populates the engine filter and raises the raises the <see cref="FilterCommand"/> event
        /// with <see cref="SearchFilterCommands.FilterStateChanged"/>, <see cref="SearchFilterCommands.Search"/>
        /// and <see cref="SearchFilterCommands.FindOrManageFiltersForActor"/> flags.</remarks>
        [Category("Appearance")]
        [Description("Width if the find filter button control.")]
        [DefaultValue(typeof(Unit), "")]
        public virtual Unit FindFilterButtonWidth
        {
            get { return this.btnFindFilterForActor.Width; }
            set 
            { 
                this.btnFindFilterForActor.Width = value;
                this.btnFindFilterForActorCustom.Width = value;
            }
        }

        /// <summary>
        /// Gets or sets the Cascading Style Sheet (CSS) class applied to the <b>find filter button</b> control.
        /// </summary>
        /// <remarks>The save button populates the engine filter and raises the raises the <see cref="FilterCommand"/> event
        /// with <see cref="SearchFilterCommands.FilterStateChanged"/>, <see cref="SearchFilterCommands.Search"/>
        /// and <see cref="SearchFilterCommands.FindOrManageFiltersForActor"/> flags.</remarks>
        [Category("Appearance")]
        [Description("CSS class applied to the find filter button control.")]
        [DefaultValue("")]
        public virtual string FindFilterButtonCssClass
        {
            get { return this.btnFindFilterForActor.CssClass; }
            set 
            { 
                this.btnFindFilterForActor.CssClass = value;
                this.btnFindFilterForActorCustom.CssClass = value;
            }
        }

        /// <summary>
        /// Gets or sets a value that indicates whether the <b>find filter button</b> control is rendered on the page. 
        /// </summary>
        /// <remarks>The save button populates the engine filter and raises the raises the <see cref="FilterCommand"/> event
        /// with <see cref="SearchFilterCommands.FilterStateChanged"/>, <see cref="SearchFilterCommands.Search"/>
        /// and <see cref="SearchFilterCommands.FindOrManageFiltersForActor"/> flags.</remarks>
        [Category("Appearance")]
        [DefaultValue(typeof(bool), "false")]
        [Description("Indicates whether the find filter button control is rendered on the page.")]
        public virtual bool FindFilterButtonVisible
        {
            get { return this.findFilterButtonVisible; }
            set { this.findFilterButtonVisible = value; }
        }

        /// <summary>
        /// Gets or sets client-side script executed when the <b>find filter button</b> is clicked.
        /// </summary>
        /// <remarks>The save button populates the engine filter and raises the raises the <see cref="FilterCommand"/> event
        /// with <see cref="SearchFilterCommands.FilterStateChanged"/>, <see cref="SearchFilterCommands.Search"/>
        /// and <see cref="SearchFilterCommands.FindOrManageFiltersForActor"/> flags.</remarks>
        [Category("Behavior")]
        [Description("Client-side script executed when the find filter button is clicked.")]
        [DefaultValue(null)]
        public virtual string FindFilterButtonScript
        {
            get { return this.btnFindFilterForActor.Attributes["onclick"]; }
            set 
            { 
                this.btnFindFilterForActor.Attributes["onclick"] = value;
                this.btnFindFilterForActorCustom.Attributes["onclick"] = value;
            }
        }

        /// <summary>
        /// Gets or sets <b>find filter button</b> template.
        /// </summary>
        /// <remarks>No effect if <see cref="FindFilterButtonType"/> is not se to <see cref="ButtonType.TemplateButton"/>.</remarks>
        [DefaultValue(null)]
        [Description("HTML template/format string for find filter button.")]
        [Category("Behavior")]
        public virtual string FindFilterButtonTemplate
        {
            get { return this.btnFindFilterForActorCustom.Template; }
            set { this.btnFindFilterForActorCustom.Template = value; }
        }

        /// <summary>
        /// Gets or sets semicolon delimitted <see cref="TemplateButton"/> property names whose values are formatted as defined by <see cref="FindFilterButtonTemplate"/>.
        /// </summary>
        /// <remarks>No effect if <see cref="FindFilterButtonType"/> is not se to <see cref="ButtonType.TemplateButton"/>.</remarks>
        [DefaultValue("")]
        [Description("Semicolon delimitted TemplateButton properties which are formatted as defined by FindFilterButtonTemplate.")]
        [Category("Behavior")]
        public virtual string FindFilterButtonTemplateArguments
        {
            get { return this.btnFindFilterForActorCustom.TemplateArguments; }
            set { this.btnFindFilterForActorCustom.TemplateArguments = value; }
        }

        /// <summary>
        /// Gets or sets optional client side scripts which are used in <see cref="FindFilterButtonTemplate"/>.
        /// </summary>
        /// <remarks>No effect if <see cref="FindFilterButtonType"/> is not set to <see cref="ButtonType.TemplateButton"/>.</remarks>
        [DefaultValue(null)]
        [Description("Client side scripts which are used in FindFilterButtonTemplate.")]
        [Category("Behavior")]
        public virtual string FindFilterButtonTemplateScript
        {
            get { return this.btnFindFilterForActorCustom.ClientScriptBlock; }
            set { this.btnFindFilterForActorCustom.ClientScriptBlock = value; }
        }

        /// <summary>
        /// Gets or sets semicolon delimitted JavaScript URLs/files which should be included in the page.
        /// </summary>
        /// <remarks>No effect if <see cref="FindFilterButtonType"/> is not se to <see cref="ButtonType.TemplateButton"/>.</remarks>
        [DefaultValue("")]
        [Description("Semicolon delimitted JavaScript URLs/files which should be included in the page.")]
        [Category("Behavior")]
        public virtual string FindFilterButtonTemplateScriptIncludeUrls
        {
            get { return this.btnFindFilterForActorCustom.ClientScriptIncludeUrls; }
            set { this.btnFindFilterForActorCustom.ClientScriptIncludeUrls = value; }
        }

        #endregion        

        #region Open predefined filter.

        /// <summary>
        /// Gets or sets the text caption of the <b>open predefined filter</b> control.
        /// </summary>
        /// <remarks>The open predefined filter control populates the engine filter with the selected filter state
        /// and raises the <see cref="FilterCommand"/> event with <see cref="SearchFilterCommands.FilterStateChanged"/> 
        /// and <see cref="SearchFilterCommands.Search"/> flags. If the selected filter is invalid, the
        /// engine's filter is reset and <see cref="SearchFilterCommands.Reset"/> flag is set.</remarks>
        [Category("Appearance")]
        [Description("Text caption of the open predefined filter control.")]
        [DefaultValue(null)]
        public virtual string OpenPredefinedFilterText
        {
            get { return this.TextSource.OpenPredefinedFilterText ?? this.openPredefinedFilterCaption; }
            set { this.openPredefinedFilterCaption = value; }
        }

        /// <summary>
        /// Gets or sets the width of the <b>open predefined filter</b> control.
        /// </summary>
        /// <remarks>The open predefined filter control populates the engine filter with the selected filter state
        /// and raises the <see cref="FilterCommand"/> event with <see cref="SearchFilterCommands.FilterStateChanged"/> 
        /// and <see cref="SearchFilterCommands.Search"/> flags. If the selected filter is invalid, the
        /// engine's filter is reset and <see cref="SearchFilterCommands.Reset"/> flag is set.</remarks>
        [Category("Appearance")]
        [Description("Width if the open predefined filter control.")]
        [DefaultValue(typeof(Unit), "")]
        public virtual Unit OpenPredefinedFilterWidth
        {
            get { return this.cmbOpenPredefinedFilter.Width; }
            set { this.cmbOpenPredefinedFilter.Width = value; }
        }

        /// <summary>
        /// Gets or sets the Cascading Style Sheet (CSS) class applied to the <b>open predefined filter</b> control.
        /// </summary>
        /// <remarks>The open predefined filter control populates the engine filter with the selected filter state
        /// and raises the <see cref="FilterCommand"/> event with <see cref="SearchFilterCommands.FilterStateChanged"/> 
        /// and <see cref="SearchFilterCommands.Search"/> flags. If the selected filter is invalid, the
        /// engine's filter is reset and <see cref="SearchFilterCommands.Reset"/> flag is set.</remarks>
        [Category("Appearance")]
        [Description("CSS class applied to the open predefined filter control.")]
        [DefaultValue("")]
        public virtual string OpenPredefinedFilterCssClass
        {
            get { return this.cmbOpenPredefinedFilter.CssClass; }
            set { this.cmbOpenPredefinedFilter.CssClass = value; }
        }

        /// <summary>
        /// Gets or sets a value that indicates whether the <b>open predefined filter</b> control is rendered on the page. 
        /// </summary>
        /// <remarks>The open predefined filter control populates the engine filter with the selected filter state
        /// and raises the <see cref="FilterCommand"/> event with <see cref="SearchFilterCommands.FilterStateChanged"/> 
        /// and <see cref="SearchFilterCommands.Search"/> flags. If the selected filter is invalid, the
        /// engine's filter is reset and <see cref="SearchFilterCommands.Reset"/> flag is set.</remarks>
        [Category("Appearance")]
        [DefaultValue(typeof(bool), "false")]
        [Description("Indicates whether the open predefined filter control is rendered on the page. ")]
        public virtual bool OpenPredefinedFilterVisible
        {
            get { return this.cmbOpenPredefinedFilter.Visible; }
            set {this.cmbOpenPredefinedFilter.Visible = value;}
        }

        /// <summary>
        /// Gets or sets client-side script executed when selected filter in the <b>open predefined filter</b> control is changed.
        /// </summary>
        /// <remarks>The open predefined filter control populates the engine filter with the selected filter state
        /// and raises the <see cref="FilterCommand"/> event with <see cref="SearchFilterCommands.FilterStateChanged"/> 
        /// and <see cref="SearchFilterCommands.Search"/> flags. If the selected filter is invalid, the
        /// engine's filter is reset and <see cref="SearchFilterCommands.Reset"/> flag is set.</remarks>
        [Category("Behavior")]
        [Description("Client-side script executed when selected filter in the open predefined filter control is changed.")]
        [DefaultValue(null)]
        public virtual string OpenPredefinedFilterScript
        {
            get { return this.cmbOpenPredefinedFilter.Attributes["onchange"]; }
            set { this.cmbOpenPredefinedFilter.Attributes["onchange"] = value; }
        }

        #endregion

        #region Generated field controls' style.

        /// <summary>
        /// Gets or sets the image of the control which collapses value interval. 
        /// Ie. hides the control which populates <see cref="ISearchEngineField.EndValue"/> property.
        /// </summary>
        [Category("Appearance")]
        [Description("Image of the control which collapses value interval. ")]
        [DefaultValue(null)]
        public virtual string CollapseIntervalImageUrl
        {
            get { return this.collapseIntervalImageUrl; }
            set { this.collapseIntervalImageUrl = value; }
        }

        /// <summary>
        /// Gets or sets the image of the control which expands value interval. 
        /// Ie. displays the control which populates <see cref="ISearchEngineField.EndValue"/> property.
        /// </summary>
        [Category("Appearance")]
        [Description("Image of the control which expands value interval. ")]
        [DefaultValue(null)]
        public virtual string ExpandIntervalImageUrl
        {
            get { return this.expandIntervalImageUrl; }
            set { this.expandIntervalImageUrl = value; }
        }        

        /// <summary>
        /// Gets or sets the width of the generated controls.
        /// </summary>
        [Category("Appearance")]
        [Description("Width of the generated controls.")]
        [DefaultValue(typeof(Unit), "")]
        public virtual Unit FieldControlWidth
        {
            get { return this.fieldControlWidth; }
            set { this.fieldControlWidth = value; }
        }        

        /// <summary>
        /// Gets or sets the Cascading Style Sheet (CSS) class applied to generated <b>DropDownList</b> controls.
        /// </summary>
        [Category("Appearance")]
        [Description("CSS class applied to generated DropDownList controls.")]
        [DefaultValue(null)]
        public virtual string DropDownListCssClass
        {
            get { return this.dropDownListCssClass; }
            set { this.dropDownListCssClass = value; }
        }

        /// <summary>
        /// Gets or sets the Cascading Style Sheet (CSS) class applied to generated <b>TextBox</b> controls.
        /// </summary>
        [Category("Appearance")]
        [Description("CSS class applied to generated TextBox controls.")]
        [DefaultValue(null)]
        public virtual string TextBoxCssClass
        {
            get { return this.textBoxCssClass; }
            set { this.textBoxCssClass = value; }
        }

        /// <summary>
        /// Gets or sets the Cascading Style Sheet (CSS) class applied to generated <b>CheckBox</b> controls.
        /// </summary>
        [Category("Appearance")]
        [Description("CSS class applied to generated CheckBox controls.")]
        [DefaultValue(null)]
        public virtual string CheckBoxCssClass
        {
            get { return this.checkBoxCssClass; }
            set { this.checkBoxCssClass = value; }
        }        

        #region Calendar style.

        /// <summary>
        /// Gets or sets the lower bounded date the user can select in the generated <b>CalendarPopup</b> controls.
        /// </summary>
        [Category("Behavior")]
        [Description("Lower bounded date the user can select in the generated CalendarPopup controls.")]
        [DefaultValue(typeof(DateTime), "1/1/0001")]
        public virtual DateTime CalendarLowerBoundDate
        {
            get { return this.calendarLowerBoundDate; }
            set { this.calendarLowerBoundDate = value; }
        }

        /// <summary>
        /// Gets or sets the upper bounded date the user can select in the generated <b>CalendarPopup</b> controls.
        /// </summary>
        [Category("Behavior")]
        [Description("Upper bounded date the user can select in the generated CalendarPopup controls.")]
        [DefaultValue(typeof(DateTime), "1/1/0001")]
        public virtual DateTime CalendarUpperBoundDate
        {
            get { return this.calendarUpperboundDate; }
            set { this.calendarUpperboundDate = value; }
        }

        /// <summary>
        /// Gets or sets the URL of the image that is displayed in the generated <b>CalendarPopup</b> controls.
        /// </summary>
        [Category("Appearance")]
        [Description("URL of the image that is displayed in the generated CalendarPopup controls.")]
        [DefaultValue(null)]
        public virtual string CalendarImageUrl
        {
            get { return this.calendarImageUrl; }
            set { this.calendarImageUrl = value; }
        }

        #endregion

        #endregion

        #endregion

        #region Events.

        /// <summary>
        /// Occurs when the engine's filter is created or changed.
        /// </summary>
        [Description("Occurs when the engine's filter is created or changed.")]
        public event EventHandler<SearchFilterCommandEventArgs> FilterCommand;

        /// <summary>
        /// Raises the <see cref="FilterCommand"/> event.
        /// </summary>
        /// <param name="e">The <see cref="SearchFilterCommandEventArgs"/> that contain the event data.</param>
        protected virtual void OnFilterChanged(SearchFilterCommandEventArgs e)
        {
            if (this.FilterCommand != null)
                this.FilterCommand(this, e);
        }

        #endregion

        #region GetDesignTimeHtml.

        internal string GetDesignTimeHtml()
        {
            StringBuilder htm = new StringBuilder();
            htm.Append("<table>");
            htm.Append("<tr>");
            htm.Append("<td>First name</td>");
            htm.Append("<td><input type=\"text\" /></td>");
            htm.Append("</tr>");
            htm.Append("<tr>");
            htm.Append("<td>Last name</td>");
            htm.Append("<td><input type=\"text\" /></td>");
            htm.Append("</tr>");
            htm.Append("</table>");
            return htm.ToString();
        }

        #endregion

        #region Settings.

        /// <summary>
        /// Gets <see cref="SearchFilter"/> settings.
        /// </summary>
        /// <returns>A collection of key-value property pairs guaranteed to work only with the <see cref="SearchFilter"/> objects.</returns>
        public virtual IDictionary<string, object> GetSettings()
        {
            Dictionary<string, object> settings = new Dictionary<string, object>();
            if (this.Formatter != null)
                settings.Add("Formatter", this.Formatter);

            settings.Add("InvertFilterEnabled", this.InvertFilterEnabled);
            settings.Add("AddWithOrEnabled", this.AddWithOrEnabled);
            settings.Add("AnyWordOrPhraseEnabled", this.AnyWordOrPhraseEnabled);
            settings.Add("IgnorePhrases", this.IgnorePhrases);
            settings.Add("TextSearchModeSelectionEnabled", this.TextSearchModeSelectionEnabled);
            settings.Add("IsNullEnabled", this.IsNullEnabled);
            AddNonDefaultString(settings, "InvertFilterText", this.InvertFilterText);
            AddNonDefaultString(settings, "AddWithOrText", this.AddWithOrText);
            AddNonDefaultString(settings, "AnyWordOrPhraseText", this.AnyWordOrPhraseText);
            AddNonDefaultString(settings, "BannedChars", this.BannedChars);
            AddNonDefaultString(settings, "TextSearchModeDefaultCaption", this.TextSearchModeDefaultCaption);
            AddNonDefaultString(settings, "TextSearchModeAnywhereInTextCaption", this.TextSearchModeAnywhereInTextCaption);
            AddNonDefaultString(settings, "TextSearchModeBeginningOfTextCaption", this.TextSearchModeBeginningOfTextCaption);
            AddNonDefaultString(settings, "TextSearchModeExactMatchCaption", this.TextSearchModeExactMatchCaption);
            AddNonDefaultString(settings, "IsNullText", this.IsNullText);
            AddNonDefaultString(settings, "AddWithOrToolTip", this.AddWithOrToolTip);
            AddNonDefaultString(settings, "InvertFilterToolTip", this.InvertFilterToolTip);
            AddNonDefaultString(settings, "AnyWordOrPhraseToolTip", this.AnyWordOrPhraseToolTip);
            AddNonDefaultString(settings, "IsNullToolTip", this.IsNullToolTip);
            AddNonDefaultString(settings, "ShowAdvancedOptionsCaption", this.ShowAdvancedOptionsCaption);
            AddNonDefaultString(settings, "HideAdvancedOptionsCaption", this.HideAdvancedOptionsCaption);
            settings.Add("AdvancedOptionsVisible", this.AdvancedOptionsVisible);
            settings.Add("ToggleAdvancedOptionsButtonVisible", this.ToggleAdvancedOptionsButtonVisible);
            settings.Add("MaxWordCount", this.MaxWordCount);
            if (this.SearchFieldProperties.Count > 0)
                settings.Add("SearchFieldProperties", this.SearchFieldProperties);
            if (this.ControlSharing.Count > 0)
                settings.Add("ControlSharing", this.ControlSharing.Clone());
            settings.Add("IntervalCriteria", this.IntervalCriteria);

            AddNonDefaultString(settings, "CollapseIntervalImageUrl", this.CollapseIntervalImageUrl);
            AddNonDefaultString(settings, "ExpandIntervalImageUrl", this.ExpandIntervalImageUrl);
            AddNonDefaultString(settings, "CssClass", this.CssClass);
            AddNonDefaultString(settings, "DropDownListCssClass", this.DropDownListCssClass);
            AddNonDefaultString(settings, "TextBoxCssClass", this.TextBoxCssClass);
            AddNonDefaultString(settings, "CheckBoxCssClass", this.CheckBoxCssClass);
            AddNonDefaultUnit(settings, "FieldControlWidth", this.FieldControlWidth);

            settings.Add("SearchButtonType", this.SearchButtonType);
            AddNonDefaultString(settings, "SearchButtonText", this.SearchButtonText);
            AddNonDefaultString(settings, "SearchButtonCssClass", this.SearchButtonCssClass);
            AddNonDefaultUnit(settings, "SearchButtonWidth", this.SearchButtonWidth);
            settings.Add("SearchButtonVisible", this.SearchButtonVisible);
            AddNonDefaultString(settings, "SearchButtonScript", this.SearchButtonScript);
            AddNonDefaultString(settings, "SearchButtonTemplate", this.SearchButtonTemplate);
            AddNonDefaultString(settings, "SearchButtonTemplateArguments", this.SearchButtonTemplateArguments);
            AddNonDefaultString(settings, "SearchButtonTemplateScript", this.SearchButtonTemplateScript);
            AddNonDefaultString(settings, "SearchButtonTemplateScriptIncludeUrls", this.SearchButtonTemplateScriptIncludeUrls);

            settings.Add("ResetButtonType", this.ResetButtonType);
            AddNonDefaultString(settings, "ResetButtonCssClass", this.ResetButtonCssClass);
            AddNonDefaultString(settings, "ResetButtonText", this.ResetButtonText);
            AddNonDefaultUnit(settings, "ResetButtonWidth", this.ResetButtonWidth);
            settings.Add("ResetButtonVisible", this.ResetButtonVisible);
            AddNonDefaultString(settings, "ResetButtonScript", this.ResetButtonScript);
            AddNonDefaultString(settings, "ResetButtonTemplate", this.ResetButtonTemplate);
            AddNonDefaultString(settings, "ResetButtonTemplateArguments", this.ResetButtonTemplateArguments);
            AddNonDefaultString(settings, "ResetButtonTemplateScript", this.ResetButtonTemplateScript);
            AddNonDefaultString(settings, "ResetButtonTemplateScriptIncludeUrls", this.ResetButtonTemplateScriptIncludeUrls);
            settings.Add("OverwriteAutoPopulatedFields", this.OverwriteAutoPopulatedFields);
            settings.Add("OverwriteHiddenFields", this.OverwriteHiddenFields);
            settings.Add("ResetAutoPopulatedFields", this.ResetAutoPopulatedFields);
            settings.Add("ResetHiddenFields", this.ResetHiddenFields);

            settings.Add("SaveButtonType", this.SaveButtonType);
            AddNonDefaultString(settings, "SaveButtonCssClass", this.SaveButtonCssClass);
            AddNonDefaultString(settings, "SaveButtonText", this.SaveButtonText);
            AddNonDefaultUnit(settings, "SaveButtonWidth", this.SaveButtonWidth);
            settings.Add("SaveButtonVisible", this.SaveButtonVisible);
            AddNonDefaultString(settings, "SaveButtonScript", this.SaveButtonScript);
            AddNonDefaultString(settings, "SaveButtonTemplate", this.SaveButtonTemplate);
            AddNonDefaultString(settings, "SaveButtonTemplateArguments", this.SaveButtonTemplateArguments);
            AddNonDefaultString(settings, "SaveButtonTemplateScript", this.SaveButtonTemplateScript);
            AddNonDefaultString(settings, "SaveButtonTemplateScriptIncludeUrls", this.SaveButtonTemplateScriptIncludeUrls);

            settings.Add("FindFilterButtonType", this.FindFilterButtonType);
            AddNonDefaultString(settings, "FindFilterButtonCssClass", this.FindFilterButtonCssClass);
            AddNonDefaultString(settings, "FindFilterButtonText", this.FindFilterButtonText);
            AddNonDefaultUnit(settings, "FindFilterButtonWidth", this.FindFilterButtonWidth);
            settings.Add("FindFilterButtonVisible", this.FindFilterButtonVisible);
            AddNonDefaultString(settings, "FindFilterButtonScript", this.FindFilterButtonScript);
            AddNonDefaultString(settings, "FindFilterButtonTemplate", this.FindFilterButtonTemplate);
            AddNonDefaultString(settings, "FindFilterButtonTemplateArguments", this.FindFilterButtonTemplateArguments);
            AddNonDefaultString(settings, "FindFilterButtonTemplateScript", this.FindFilterButtonTemplateScript);
            AddNonDefaultString(settings, "FindFilterButtonTemplateScriptIncludeUrls", this.FindFilterButtonTemplateScriptIncludeUrls);

            AddNonDefaultString(settings, "OpenPredefinedFilterCssClass", this.OpenPredefinedFilterCssClass);
            AddNonDefaultString(settings, "OpenPredefinedFilterText", this.OpenPredefinedFilterText);
            AddNonDefaultUnit(settings, "OpenPredefinedFilterWidth", this.OpenPredefinedFilterWidth);
            settings.Add("OpenPredefinedFilterVisible", this.OpenPredefinedFilterVisible);
            AddNonDefaultString(settings, "OpenPredefinedFilterScript", this.OpenPredefinedFilterScript);

            AddNonDefaultString(settings, "CalendarImageUrl", this.CalendarImageUrl);
            AddNonDefaultDate(settings, "CalendarLowerBoundDate", this.CalendarLowerBoundDate);
            AddNonDefaultDate(settings, "CalendarUpperBoundDate", this.CalendarUpperBoundDate);

            // Lookups.
            settings.Add("LookupTextQueryKeyGenerator", this.LookupTextQueryKeyGenerator);
            AddNonDefaultString(settings, "LookupSkinID", this.LookupSkinID);
            settings.Add("LookupReadOnly", this.LookupReadOnly);
            AddNonDefaultString(settings, "LookupTextQueryKeyPrefix", this.LookupTextQueryKeyPrefix);
            AddNonDefaultString(settings, "LookupBuilderSkinID", this.LookupBuilderSkinID);
            AddNonDefaultString(settings, "LookupFilterComponentAssembly", this.LookupFilterComponentAssembly);
            AddNonDefaultString(settings, "LookupFilterComponentClass", this.LookupFilterComponentClass);
            AddNonDefaultString(settings, "LookupFilterComponentSkinID", this.LookupFilterComponentSkinID);
            AddNonDefaultString(settings, "LookupViewComponentAssembly", this.LookupViewComponentAssembly);
            AddNonDefaultString(settings, "LookupViewComponentClass", this.LookupViewComponentClass);
            AddNonDefaultString(settings, "LookupViewComponentSkinID", this.LookupViewComponentSkinID);
            AddNonDefaultString(settings, "AutoCompleteSkinID", this.AutoCompleteSkinID);

            return settings;
        }

        private static void AddNonDefaultDate(IDictionary<string, object> settings, string key, DateTime val)
        {
            if (val != default(DateTime))
                settings.Add(key, val);
        }

        private static void AddNonDefaultString(IDictionary<string, object> settings, string key, string val)
        {
            if (!string.IsNullOrEmpty(val))
                settings.Add(key, val);
        }

        private static void AddNonDefaultStyle(IDictionary<string, object> settings, string key, Style val)
        {
            if (val != null && !val.IsEmpty)
                settings.Add(key, val);
        }

        private static void AddNonDefaultUnit(IDictionary<string, object> settings, string key, Unit val)
        {
            if (val != default(Unit))
                settings.Add(key, val);
        }

        /// <summary>
        /// Applies the settings created by this or another <see cref="SearchFilter"/> instance to the current instance.
        /// </summary>
        /// <param name="settings">Settings collection which was created by the <see cref="SearchFilter"/> or compatibile class.</param>
        public virtual void ApplySettings(IDictionary<string, object> settings)
        {
            foreach (KeyValuePair<string, object> namedValue in settings)
            {
                object val = namedValue.Value;
                if (val == null)
                    continue;

                PropertyInfo property = GetType().GetProperty(namedValue.Key);
                if (property != null)
                {
                    if (property.CanWrite)
                    {
                        if (property.PropertyType.IsAssignableFrom(val.GetType()))
                            property.SetValue(this, val, null);
                    }
                }
            }
        }

        #endregion

        #region OnPreRender.

        /// <summary>
        /// Raises the <b>PreRender</b> event.
        /// </summary>
        /// <param name="e">An <see cref="EventArgs"/> object that contains the event data.</param>
        protected override void OnPreRender(EventArgs e)
        {
            ApplyTextSourceToChildControls();
            ApplyTextSourceToButtons();
            foreach (Pair<ISearchEngineField, BoundLookup> fieldAndLookup in this.lookupsToRegister)
                RegisterLookup(fieldAndLookup.First, fieldAndLookup.Second);
            foreach (Pair<ISearchEngineField, BoundAutoComplete> fieldAndLookup in this.autoCompletesToRegister)
                RegisterAutoComplete(fieldAndLookup.First, fieldAndLookup.Second);

            if (!predefinedFiltersAreBound)
                BindPredefinedFilters();

            RegisterCancelBannedCharsScript();
            RegisterToggleAdvancedOptionsScript();
            ShowAdvancedOptionsIfUserIsUsingThem();

            base.OnPreRender(e);
        }

        private void ApplyTextSourceToChildControls()
        {
            if (this.TextSource.OpenPredefinedFilterText != null)
                this.OpenPredefinedFilterText = this.TextSource.OpenPredefinedFilterText;
            if (this.TextSource.ShowAdvancedOptionsCaption != null)
                this.ShowAdvancedOptionsCaption = this.TextSource.ShowAdvancedOptionsCaption;
            if (this.TextSource.HideAdvancedOptionsCaption != null)
                this.HideAdvancedOptionsCaption = this.TextSource.HideAdvancedOptionsCaption;
        }

        private void ApplyTextSourceToButtons()
        {
            if (this.TextSource.SearchButtonText != null)
                this.SearchButtonText = this.TextSource.SearchButtonText;

            if (this.TextSource.ResetButtonText != null)
                this.ResetButtonText = this.TextSource.ResetButtonText;

            if (this.TextSource.FindFilterButtonText != null)
                this.FindFilterButtonText = this.TextSource.FindFilterButtonText;

            if (this.TextSource.SaveButtonText != null)
                this.SaveButtonText = this.TextSource.SaveButtonText;
        }

        #endregion

        #region Advanced options.

        private void CreateToggleAdvancedOptionsRow()
        {
            // Show/hide advanced options.
            TableRow rowToggleAdvancedOptions = new TableRow();
            rowToggleAdvancedOptions.Visible = this.ToggleAdvancedOptionsButtonVisible;
            this.tblControlContainer.Rows.Add(rowToggleAdvancedOptions);

            // One empty cell. Toggle button is aligned with first (begin value) field control.
            rowToggleAdvancedOptions.Cells.Add(new TableCell());
            TableCell cellTogleAdvancedOptions = new TableCell() { ColumnSpan = 2 };
            rowToggleAdvancedOptions.Cells.Add(cellTogleAdvancedOptions);

            HtmlAnchor btnToggleAdvOptions = new HtmlAnchor();
            btnToggleAdvOptions.ID = ToggleAdvancedOptionsButtonId;
            // InnerHtml may change in PreRender we determine that user is using advanced options (after submited values have been loaded).
            btnToggleAdvOptions.InnerHtml = (this.AdvancedOptionsVisible) ? this.HideAdvancedOptionsCaption : this.ShowAdvancedOptionsCaption;
            btnToggleAdvOptions.HRef = string.Format("javascript:{0}(); ", GetToggleAdvOptionsScriptName());
            cellTogleAdvancedOptions.Controls.Add(btnToggleAdvOptions);
        }

        private void CreateAdvancedOptionsCell(ISearchEngineField field, TableRow controlRow)
        {
            // Is null, Invert, AND/OR, any word/phrase, text mode.
            TableCell advancedOptionsCell = new TableCell();
            advancedOptionsCell.ID = GetAdvancedOptionsCellId(field);
            // This may change in PreRender we determine that user is using advanced options (after submited values have been loaded).
            advancedOptionsCell.Style["display"] = (this.AdvancedOptionsVisible) ? "" : "none";
            controlRow.Cells.Add(advancedOptionsCell);

            if (this.IsNullEnabled)
            {
                advancedOptionsCell.Controls.Add(CreateIsNullCheckBox(field));
                advancedOptionsCell.Controls.Add(new LiteralControl("&nbsp;"));
            }

            // AND/OR. FieldPermissions which share a single control must be OR-ed.
            bool sharesInputControlWithOtherFields = this.ControlSharing.ContainsField(field.BindingName);
            if (this.AddWithOrEnabled && !sharesInputControlWithOtherFields)
            {
                advancedOptionsCell.Controls.Add(CreateAddWithOrCheckBox(field));
                advancedOptionsCell.Controls.Add(new LiteralControl("&nbsp;"));
            }

            if (this.InvertFilterEnabled)
            {
                advancedOptionsCell.Controls.Add(CreateNegateCheckBox(field));
                advancedOptionsCell.Controls.Add(new LiteralControl("&nbsp;"));
            }

            bool allowMultipleValues = field.AllowMultipleValues && (field.CodeList == null) && (field.LookupBuilder == null) && (TypeUtil.IsText(field.DataType) || TypeUtil.IsNumber(field.DataType));
            if (this.AnyWordOrPhraseEnabled && allowMultipleValues)
            {
                advancedOptionsCell.Controls.Add(CreateAnyWordCheckBox(field));
                advancedOptionsCell.Controls.Add(new LiteralControl("&nbsp;"));
            }

            bool mayChooseTextMode = this.TextSearchModeSelectionEnabled && (field.LookupBuilder == null) && (field.CodeList == null) && TypeUtil.IsText(field.DataType) && (field.AllowedTextSearchModes.Length > 1);
            if (mayChooseTextMode)
            {
                advancedOptionsCell.Controls.Add(CreateSelectTextModeDdl(field));
                advancedOptionsCell.Controls.Add(new LiteralControl("&nbsp;"));
            }
        }

        private string GetToggleAdvOptionsScriptName()
        {
            return this.ClientID + "_ToggleAdvOptions";
        }

        private void RegisterToggleAdvancedOptionsScript()
        {
            StringBuilder js = new StringBuilder();
            js.AppendLine();
            string bAdvOptionsVisible = (this.AdvancedOptionsVisible || IsUsingAdvancedOptions()) ? "true" : "false";
            js.AppendFormat("var {0}_AdvOptionsVisible = {1}; ", this.ClientID, bAdvOptionsVisible);
            js.AppendFormat("function {0}(){{ ", GetToggleAdvOptionsScriptName());
            js.AppendFormat("var isVisible = !{0}_AdvOptionsVisible; ", this.ClientID);
            string show = JScript.CleanString(this.ShowAdvancedOptionsCaption ?? "");
            string hide = JScript.CleanString(this.HideAdvancedOptionsCaption ?? "");
            Control btnTogggle = GetToggleAdvancedOptionsButton();
            js.AppendFormat("document.getElementById(\"{0}\").innerHTML = (isVisible) ? \"{1}\" : \"{2}\"; ", btnTogggle.ClientID, hide, show);
            js.Append("var displayStyle = (isVisible) ? \"\" : \"none\"; ");
            
            // Advanced fields' rows.
            foreach (ISearchEngineField field in this.engine.SearchFields)
            {
                if (field.Usage == SearchFieldUsage.AdvancedSearch)
                {
                    Control fieldRow = FindControl(GetRowId(field));
                    js.AppendFormat("document.getElementById(\"{0}\").style.display = displayStyle; ", fieldRow.ClientID);
                }
            }

            // Advanced options cell.s
            foreach (ISearchEngineField field in this.engine.SearchFields)
            {
                TableCell cell = GetAdvancedOptionsCell(field);
                if (cell != null)
                    js.AppendFormat("document.getElementById(\"{0}\").style.display = displayStyle; ", cell.ClientID);
            }

            js.AppendFormat("{0}_AdvOptionsVisible = isVisible; ", this.ClientID);
            js.AppendLine("} ");
            JScript.RegisterClientScriptBlock(js.ToString(), true);
        }

        private HtmlAnchor GetToggleAdvancedOptionsButton()
        {
            return FindControl(ToggleAdvancedOptionsButtonId) as HtmlAnchor;
        }

        private TableCell GetAdvancedOptionsCell(ISearchEngineField field)
        {
            return FindControl(GetAdvancedOptionsCellId(field)) as TableCell;
        }

        private void ShowAdvancedOptionsIfUserIsUsingThem()
        {
            // Show advanced options if user entered something.
            if (IsUsingAdvancedOptions())
            {
                HtmlAnchor btnTogggle = GetToggleAdvancedOptionsButton();
                btnTogggle.InnerHtml = this.HideAdvancedOptionsCaption;

                foreach (ISearchEngineField field in this.engine.SearchFields)
                {
                    TableCell cell = GetAdvancedOptionsCell(field);
                    if (cell != null)
                        cell.Style["display"] = "";
                }
            }
        }

        private bool IsUsingAdvancedOptions()
        {
            foreach (ISearchEngineField field in this.engine.SearchFields)
            {
                if (!field.IsHidden)
                {
                    bool isUsed = (this.IsNullEnabled && field.IsNull)
                        || (this.AddWithOrEnabled && field.AddWithOr)
                        || (this.InvertFilterEnabled && field.Negate)
                        || (this.AnyWordOrPhraseEnabled && (field.Values != null && field.Values.Length > 1))
                        || (this.TextSearchModeSelectionEnabled && field.SelectedTextSearchMode != TextSearchMode.Default);

                    if (isUsed)
                        return true;
                }
            }

            return false;
        }


        #endregion

        #region Cancel banned chars.

        private string GetCancelBannedCharsScriptName()
        {
            return this.ClientID + "_CheckChar";
        }

        private void RegisterCancelBannedCharsScript()
        {
            bool hasDefinedBannedChars = !string.IsNullOrEmpty(this.BannedChars);
            if (hasDefinedBannedChars)
            {
                string escapedBannedChars = this.BannedChars.Replace("'", @"\'");
                string script = " function " + GetCancelBannedCharsScriptName() + "(evt){";
                script += "var keyCode = (evt.keyCode > 0) ? evt.keyCode : evt.which; ";
                script += "if ('" + escapedBannedChars + "'.indexOf(String.fromCharCode(keyCode)) >= 0) return false;";
                script += "} ";
                Page.ClientScript.RegisterClientScriptBlock(Page.GetType(), this.ClientID + GetCancelBannedCharsScriptName(), script, true);
            }
        }

        #endregion

        #region BindPredefinedFilters.

        bool predefinedFiltersAreBound;        

        private void BindPredefinedFilters()
        {
            string selectedValue = this.cmbOpenPredefinedFilter.SelectedValue;

            this.cmbOpenPredefinedFilter.Items.Clear();
            this.cmbOpenPredefinedFilter.Items.Add(new ListItem(this.OpenPredefinedFilterText, ""));
            foreach (KeyValuePair<string, string> namedFilter in this.PredefinedSearchEngineFieldsStates)
            {
                string name = namedFilter.Key;
                string state = HttpUtility.UrlEncode(namedFilter.Value);
                // Avoid duplicate names.
                if (this.cmbOpenPredefinedFilter.Items.FindByText(name) == null)
                    this.cmbOpenPredefinedFilter.Items.Add(new ListItem(name, state));
            }

            try { this.cmbOpenPredefinedFilter.SelectedValue = selectedValue; }
            catch { this.cmbOpenPredefinedFilter.SelectedIndex = 0; }

            this.predefinedFiltersAreBound = true;
        }

        #endregion

        #region Read/write to search fields. Filter commands.

        /// <summary>
        /// Orders the SearchFilter to write its internal state and entered values into engine's search fields.
        /// </summary>
        /// <remarks>Raises the <see cref="FilterCommand"/> with <see cref="SearchFilterCommands.FilterStateChanged"/> flag.</remarks>
        public virtual void WriteToSearchFields()
        {           
            InitDynamicFilter();
        }

        /// <summary>
        /// Orders the SearchFilter to write its internal state and entered values into engine's search fields.
        /// </summary>
        /// <param name="overwriteAutoPopulatedFields">Indicates whether the search fields whose IsAutoPopulated property is set to true may be overwritten.</param>
        /// <param name="overwriteHiddenFields">Indicates whether the search fields whose IsHidden property is set to true may be overwritten.</param>
        /// <remarks>Raises the <see cref="FilterCommand"/> with <see cref="SearchFilterCommands.FilterStateChanged"/> flag.</remarks>
        public virtual void WriteToSearchFields(bool overwriteAutoPopulatedFields, bool overwriteHiddenFields)
        {
            InitDynamicFilter(overwriteAutoPopulatedFields, overwriteHiddenFields);
        }

        /// <summary>
        /// Orders the SearchFilter to set its internal state and display values so that 
        /// it matches the values stored in the engine's search fileds.
        /// </summary>
        public virtual void ReadSearchFields()
        {
            CopyEngineFilterToControls();
        }        

        void btnSearch_Click(object sender, EventArgs e)
        {
            Search();
        }

        /// <summary>
        /// Populates the engine filter and raises the <see cref="FilterCommand"/> event with 
        /// <see cref="SearchFilterCommands.FilterStateChanged"/> and <see cref="SearchFilterCommands.Search"/> flags.
        /// </summary>
        /// <remarks>This is the method which is invoked when the <b>search button</b> is clicked.</remarks>
        public virtual void Search()
        {
            this.cmbOpenPredefinedFilter.SelectedIndex = 0;
            InitDynamicFilter();
            OnFilterChanged(new SearchFilterCommandEventArgs(this, SearchFilterCommands.FilterStateChanged | SearchFilterCommands.Search));
        }

        void btnReset_Click(object sender, EventArgs e)
        {
            Reset();
        }

        /// <summary>
        /// Resets the engine filter and raises the <see cref="FilterCommand"/> event
        /// with <see cref="SearchFilterCommands.FilterStateChanged"/>, <see cref="SearchFilterCommands.Search"/>
        /// and <see cref="SearchFilterCommands.Reset"/> flags.
        /// </summary>
        /// <remarks>This is the method which is invoked when the <b>reset button</b> is clicked.</remarks>
        public virtual void Reset()
        {
            this.cmbOpenPredefinedFilter.SelectedIndex = 0;
            this.Engine.SearchFields.ResetAllFields(this.ResetAutoPopulatedFields, this.ResetHiddenFields);
            CopyEngineFilterToControls();
            OnFilterChanged(new SearchFilterCommandEventArgs(this, SearchFilterCommands.FilterStateChanged | SearchFilterCommands.Search | SearchFilterCommands.Reset));
        }

        void btnSaveFilterForActor_Click(object sender, EventArgs e)
        {
            SaveFilterForActor();
        }

        /// <summary>
        /// Populates the engine filter and raises the <see cref="FilterCommand"/> event 
        /// with <see cref="SearchFilterCommands.FilterStateChanged"/>, <see cref="SearchFilterCommands.Search"/>
        /// and <see cref="SearchFilterCommands.SaveFilterForActor"/> flags.
        /// </summary>
        /// <remarks>This is the method which is invoked when the <b>save button</b> is clicked.</remarks>
        public virtual void SaveFilterForActor()
        {
            this.cmbOpenPredefinedFilter.SelectedIndex = 0;
            InitDynamicFilter();
            OnFilterChanged(new SearchFilterCommandEventArgs(this, SearchFilterCommands.FilterStateChanged | SearchFilterCommands.Search | SearchFilterCommands.SaveFilterForActor));
        }

        void btnFindFilterForActor_Click(object sender, EventArgs e)
        {
            this.cmbOpenPredefinedFilter.SelectedIndex = 0;
            InitDynamicFilter();
            OnFilterChanged(new SearchFilterCommandEventArgs(this, SearchFilterCommands.FilterStateChanged | SearchFilterCommands.Search | SearchFilterCommands.FindOrManageFiltersForActor));
        }

        void cmbOpenPredefinedFilter_SelectedIndexChanged(object sender, EventArgs e)
        {
            OpenSelectedPredefinedFilter(this.cmbOpenPredefinedFilter.SelectedValue);
        }

        /// <summary>
        /// Populates the engine filter with the selected filter state and raises the <see cref="FilterCommand"/> 
        /// event with <see cref="SearchFilterCommands.FilterStateChanged"/> and 
        /// <see cref="SearchFilterCommands.Search"/> flags. If the selected filter is invalid, the engine's filter 
        /// is reset and <see cref="SearchFilterCommands.Reset"/> flag is set.
        /// </summary>
        /// <param name="filterState">One of the filter states defined in the <see cref="PredefinedSearchEngineFieldsStates"/> collection.</param>
        public void SelectPredefinedFilter(string filterState)
        {
            if (!string.IsNullOrEmpty(filterState) && filterState[0] == '<')
                filterState = HttpUtility.UrlEncode(filterState);

            try { this.cmbOpenPredefinedFilter.SelectedValue = filterState; }
            catch { this.cmbOpenPredefinedFilter.SelectedIndex = 0; };

            OpenSelectedPredefinedFilter(filterState);
        }

        private void OpenSelectedPredefinedFilter(string selectedValue)
        {
            string state = HttpUtility.UrlDecode(selectedValue);

            bool resetFilter = false;
            if (state.Length == 0)
                resetFilter = true;

            if (!resetFilter)
            {
                try
                {
                    this.Engine.SearchFields.ResetAllFields(this.OverwriteAutoPopulatedFields, this.OverwriteHiddenFields);
                    SearchEngineFieldsState.Deserialize(this.Engine.SearchFields, state, this.OverwriteAutoPopulatedFields, this.OverwriteHiddenFields);
                    CopyEngineFilterToControls();
                }
                catch (Exception e)
                {
                    Log.AppWarning("SearchFilter.OpenSelectedPredefinedFilter", e.ToString(), null);
                    resetFilter = true;
                }
            }

            if (resetFilter)
            {
                this.cmbOpenPredefinedFilter.SelectedIndex = 0;
                this.Engine.SearchFields.ResetAllFields(this.OverwriteAutoPopulatedFields, this.OverwriteHiddenFields);
                CopyEngineFilterToControls();
                OnFilterChanged(new SearchFilterCommandEventArgs(this, SearchFilterCommands.FilterStateChanged | SearchFilterCommands.Search | SearchFilterCommands.Reset));
            }
            else
            {
                OnFilterChanged(new SearchFilterCommandEventArgs(this, SearchFilterCommands.FilterStateChanged | SearchFilterCommands.Search));
            }
        }

        private void CopyEngineFilterToControls()
        {
            foreach (ISearchEngineField field in this.OrderedFields)
            {
                if (!field.IsAutoPopulated)
                {
                    SetControlValue(field, false, field.BeginValue);
                    SetControlValue(field, true, field.EndValue);
                    SetControlMultipleValues(field);
                    if (field.EndValue != null)
                        ExpandEndValueCell(field);
                    else
                        CollapseEndValueCell(field);

                    CheckBox chkIsNull = GetIsNullCheckBox(field);
                    if (chkIsNull != null)
                        chkIsNull.Checked = field.IsNull;

                    CheckBox chkAddWithOr = GetAddWithOrCheckBox(field);
                    if (chkAddWithOr != null)
                        chkAddWithOr.Checked = field.AddWithOr;

                    CheckBox chkNegate = GetNegateCheckBox(field);
                    if (chkNegate != null)
                        chkNegate.Checked = field.Negate;

                    DropDownList ddlTextMode = GetSelectTextModeDdl(field);
                    if (ddlTextMode != null)
                    {
                        try { ddlTextMode.SelectedValue = field.SelectedTextSearchMode.ToString(); }
                        catch { ddlTextMode.SelectedIndex = 0; }
                    }

                    DropDownList ddlValueSets = GetValueSetsDdl(field);
                    if (ddlValueSets != null)
                    {
                        bool keySetAndDdlContainsItem = (field.SelectedValueSetKey != null 
                            && ddlValueSets.Items.FindByValue(field.SelectedValueSetKey) != null);

                        if (keySetAndDdlContainsItem)
                        {
                            try { ddlValueSets.SelectedValue = field.SelectedValueSetKey; }
                            catch { ddlValueSets.SelectedIndex = 0; }
                        }
                        else
                        {
                            ddlValueSets.SelectedIndex = 0;
                        }
                    }
                }
            }
        }

        private void InitDynamicFilter()
        {
            InitDynamicFilter(this.OverwriteAutoPopulatedFields, this.OverwriteHiddenFields);
        }

        private void InitDynamicFilter(bool overwriteAutoPopulatedFields, bool overwriteHiddenFields)
        {
            foreach (ISearchEngineField field in this.OrderedFields)
            {
                bool shouldSkipBecausItsAutoPopulated = (field.IsAutoPopulated && !overwriteAutoPopulatedFields);
                bool shouldSkipBecauseItsHidden = (field.IsHidden && !overwriteHiddenFields);                
                if (shouldSkipBecauseItsHidden || shouldSkipBecausItsAutoPopulated)
                    continue;

                bool exactMatchForBeginValue;
                object beginValue = GetValue(field, false, out exactMatchForBeginValue);
                bool exactMatchForEndValue;
                object endValue = GetValue(field, true, out exactMatchForEndValue);
                object[] multipleValues = GetMultipleValues(field);

                if (multipleValues.Length > 1)
                {
                    field.BeginValue = null;
                    field.EndValue = null;
                    field.Values = multipleValues;
                }
                else
                {
                    field.BeginValue = beginValue;
                    field.EndValue = endValue;
                    field.Values = null;
                }

                CheckBox chkIsNull = GetIsNullCheckBox(field);
                if (chkIsNull != null)
                    field.IsNull = chkIsNull.Checked;

                bool sharesInputControlWithOtherFields = this.ControlSharing.ContainsField(field.BindingName);
                if (!sharesInputControlWithOtherFields)
                {
                    CheckBox chkAddWithOr = GetAddWithOrCheckBox(field);
                    if (chkAddWithOr != null)
                        field.AddWithOr = chkAddWithOr.Checked;
                }

                CheckBox chkNegate = GetNegateCheckBox(field);
                if (chkNegate != null)
                    field.Negate = chkNegate.Checked;

                DropDownList ddlTextMode = GetSelectTextModeDdl(field);
                if (ddlTextMode != null)
                {
                    TextSearchMode? searchMode = NumberParser.ParseEnum<TextSearchMode>(ddlTextMode.SelectedValue);
                    bool isSupportedMode = (searchMode != null) && (Array.IndexOf<TextSearchMode>(field.AllowedTextSearchModes, searchMode.Value) >= 0);
                    if (isSupportedMode)
                        field.SelectedTextSearchMode = searchMode.Value;
                }
                else if (exactMatchForBeginValue)
                {
                    field.SelectedTextSearchMode = TextSearchMode.ExactMatch;
                }

                // This is as close to De Morgan as we can get with current model.
                if (sharesInputControlWithOtherFields && !field.Negate)
                    field.AddWithOr = true;

                DropDownList ddlValueSets = GetValueSetsDdl(field);
                if (ddlValueSets != null)
                {
                    string selectedValueSetKey = !string.IsNullOrEmpty(ddlValueSets.SelectedValue) ? ddlValueSets.SelectedValue : null;
                    field.SelectedValueSetKey = selectedValueSetKey;
                }                

                if (endValue != null)
                    ExpandEndValueCell(field);
                else
                    CollapseEndValueCell(field);
            }
        }

        #endregion

        #region Render. Process chosen button types.

        /// <summary>
        /// Renders the controls to the specified HTML writer.
        /// </summary>
        /// <param name="writer"></param>
        protected override void Render(HtmlTextWriter writer)
        {            
            ChooseSearchButton();
            ChooseResetButton();
            ChooseFindFilterButton();
            ChooseSaveFilterButton();            

            base.Render(writer);
        }

        private void ChooseSearchButton()
        {
            switch (this.SearchButtonType)
            {
                case ButtonType.TemplateButton:
                    this.btnSearch.Visible = false;
                    this.btnSearchCustom.Visible = this.SearchButtonVisible;
                    break;

                default:
                    this.btnSearch.Visible = this.SearchButtonVisible;
                    this.btnSearchCustom.Visible = false;
                    break;
            }
        }

        private void ChooseResetButton()
        {
            switch (this.ResetButtonType)
            {
                case ButtonType.TemplateButton:
                    this.btnReset.Visible = false;
                    this.btnResetCustom.Visible = this.ResetButtonVisible;
                    break;

                default:
                    this.btnReset.Visible = this.ResetButtonVisible;
                    this.btnResetCustom.Visible = false;
                    break;
            }
        }

        private void ChooseFindFilterButton()
        {
            switch (this.FindFilterButtonType)
            { 
                case ButtonType.TemplateButton:
                    this.btnFindFilterForActor.Visible = false;
                    this.btnFindFilterForActorCustom.Visible = this.FindFilterButtonVisible;
                    break;

                default:
                    this.btnFindFilterForActor.Visible = this.FindFilterButtonVisible;
                    this.btnFindFilterForActorCustom.Visible = this.FindFilterButtonVisible;
                    break;
            }
        }

        private void ChooseSaveFilterButton()
        {
            switch (this.SaveButtonType)
            { 
                case ButtonType.TemplateButton:
                    this.btnSaveFilterForActor.Visible = false;
                    this.btnSaveCustom.Visible = this.SaveButtonVisible;
                    break;

                default:
                    this.btnSaveFilterForActor.Visible = this.SaveButtonVisible;
                    this.btnSaveCustom.Visible = false;
                    break;
            }
        }

        #endregion                

        #region Overriden field properties.

        LevelTwoDictionary<string, string, object> searchFieldProperties = new LevelTwoDictionary<string, string, object>();

        /// <summary>
        /// Gets or sets a collection of overriden search field properties.
        /// </summary>
        /// <remarks>You can use this collection to override the properties of the search fields 
        /// (<see cref="ISearchEngine.SearchFields"/>) defined by search <see cref="Engine"/>.</remarks>
        [Browsable(false)]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public LevelTwoDictionary<string, string, object> SearchFieldProperties
        {
            get { return searchFieldProperties; }
            set
            {
                if (value == null)
                    throw new ArgumentNullException("Null is not allowed.");

                this.searchFieldProperties = value;
            }
        }

        private void OverrideFieldProperties()
        {
            if (this.SearchFieldProperties.Count == 0)
                return;

            foreach (ISearchEngineField field in this.Engine.SearchFields)
            {
                IDictionary<string, object> propertyValues = this.SearchFieldProperties.Get1stLevelDictionary(field.BindingName);
                if (propertyValues != null)
                {
                    foreach (KeyValuePair<string, object> propNameAndValue in propertyValues)
                        ReflectionUtil.TrySetPropertyValue(field, propNameAndValue.Key, propNameAndValue.Value, true);
                }
            }
        }

        #endregion

        #region Control sharing.

        ControlSharingList controlSharing = new ControlSharingList();

        /// <summary>
        /// Gets or sets a list which defines how input controls are shared between multiple search fields.
        /// </summary>
        [Browsable(false)]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public ControlSharingList ControlSharing
        {
            get { return this.controlSharing; }
            set { this.controlSharing = value; }
        }

        /// <summary>
        /// Contains "one control for multiple search fields" definitions for a <see cref="SearchFilter"/> control.
        /// </summary>
        /// <remarks><para>It is possible to use a single control to search through multiple fields. Eg. you may 
        /// want to have a single <b>TextBox</b> control to search for both first and last name of a person.</para>
        /// <para>Rules:
        /// <list type="bullet">
        /// <item>A single field can be combined with other fields only once.</item>
        /// <item><see cref="IBindableField.DataType"/> of all fields must be textual. Numeric, date and other types of data are not supported.</item>
        /// <item>Combined fields are automatically OR-ed. The <b>AddWithOr</b> checkbox is not rendered.</item>
        /// <item>If <b>Invert</b> chekbox is selected than the field are AND-ed and negated to honner De Morgan's law.</item>
        /// </list></para></remarks>
        public class ControlSharingList : ICloneable
        {
            #region Constants.

            const string captionDelimiter = " / ";
            const string descriptionDelimiter = " ";

            #endregion

            List<SharedControlDef> sharings = new List<SharedControlDef>();

            #region Internal CTor and methods.

            internal ControlSharingList()
            {
            }

            internal void LinkToEngine(ISearchEngine engine)
            {
                foreach (SharedControlDef combination in this.sharings)
                {
                    foreach (string fieldName in combination.Fields)
                    {
                        ISearchEngineField field = engine.SearchFields[fieldName];
                        if (field == null)
                            throw new InvalidOperationException("The provided engine doesn't contain the '" + field + "' field.");
                    }
                }

                foreach (SharedControlDef def in this.sharings)
                {
                    EnsureCaptionAndDescription(def, engine);
                }
            }

            private static void EnsureCaptionAndDescription(SharedControlDef combination, ISearchEngine engine)
            {
                if (combination.Caption == null)
                {
                    combination.Caption = "";
                    foreach (string field in combination.Fields)
                    {
                        string fieldCaption = engine.SearchFields[field].Caption;
                        if (!string.IsNullOrEmpty(fieldCaption))
                            combination.Caption += fieldCaption + captionDelimiter;
                    }

                    if (combination.Caption.Length > 0 && captionDelimiter.Length > 0)
                        combination.Caption = combination.Caption.Remove(combination.Caption.Length - captionDelimiter.Length);
                }

                if (combination.Description == null)
                {
                    combination.Description = "";
                    foreach (string field in combination.Fields)
                    {
                        string fieldDescription = engine.SearchFields[field].Description;
                        if (!string.IsNullOrEmpty(fieldDescription))
                            combination.Description += fieldDescription + descriptionDelimiter;
                    }

                    if (combination.Description.Length > 0 && descriptionDelimiter.Length > 0)
                        combination.Description = combination.Description.Remove(combination.Description.Length - descriptionDelimiter.Length);
                }
            }

            internal int GetCombinationIndexForField(string field)
            {
                for (int idxCombination = 0; idxCombination < this.sharings.Count; idxCombination++)
                {
                    SharedControlDef currCombination = this.sharings[idxCombination];
                    foreach (string existingField in currCombination.Fields)
                    {
                        if (existingField == field)
                            return idxCombination;
                    }
                }

                return -1;
            }

            internal SharedControlDef GetCombination(int index)
            {
                return this.sharings[index];
            }

            internal SharedControlDef GetCombination(string field)
            {
                int index = GetCombinationIndexForField(field);
                return this.sharings[index];
            }

            #endregion

            #region Public methods: Add, Remove, Clear.

            /// <summary>
            /// Adds a new field combination which shares the same input control
            /// </summary>
            /// <param name="fields">Two or more textual search fields which will share the same input control.</param>
            /// <remarks><para>The method will automatically generate the caption, description and ordinal for the new 
            /// entry. Caption and decription are generated as a combination of captions and descriptions of the
            /// specified <see cref="ISearchEngineField"/> objects provided by the <see cref="SearchFilter.Engine"/>.
            /// The ordinal (control position) is set to the minimum <see cref="IBindableField.Ordinal"/> value 
            /// defined by the specified search fields.</para></remarks>
            public void Add(string[] fields)
            {
                CheckNewEntry(fields);
                this.sharings.Add(new SharedControlDef(fields, null, null));
            }

            /// <summary>
            /// Adds a new field combination which shares the same input control
            /// </summary>
            /// <param name="fields">Two or more textual search fields which will share the same input control.</param>
            /// <param name="caption">Caption rendered in front of control.</param>
            /// <param name="description">Description of the field combination.</param>
            /// <remarks>The ordinal (control position) is set to the minimum <see cref="IBindableField.Ordinal"/> 
            /// value defined by the specified search fields.</remarks>
            public void Add(string[] fields, string caption, string description)
            {
                CheckNewEntry(fields);
                this.sharings.Add(new SharedControlDef(fields, caption, description));
            }

            private void CheckNewEntry(string[] fields)
            {
                foreach (string newField in fields)
                {
                    if (ContainsField(newField))
                        throw new InvalidOperationException("The current instance already defines a sharing combination for the '" + newField + "' field.");
                }
            }

            /// <summary>
            /// Checks whether the current instance contains a combination which contains the specified field.
            /// </summary>
            /// <param name="field">Search field.</param>
            /// <returns><b>true</b> if a combination is defined for the specified field; <b>false</b> otherwise.</returns>
            public bool ContainsField(string field)
            {
                int idxOfCombinationContainingTheField = GetCombinationIndexForField(field);
                return (idxOfCombinationContainingTheField < 0) ? false : true;
            }

            /// <summary>
            /// Checks whether the current instance contains the specified field combination.
            /// </summary>
            /// <param name="fields">Two or more fields.</param>
            /// <returns><b>true</b> if the combination exists; <b>false</b> otherwise.</returns>
            private bool ContainsCombination(string[] fields)
            {
                // FieldPermissions are sorted in SharedControlDef.
                string[] sortedFields = (string[])fields.Clone();
                Array.Sort<string>(sortedFields);
                foreach (SharedControlDef existingCombination in this.sharings)
                {
                    if (ArraysAreEqual(existingCombination.Fields, sortedFields))
                        return true;
                }

                return false;
            }

            private static bool ArraysAreEqual(string[] left, string[] right)
            {
                if (left.Length != right.Length)
                    return false;

                for (int idxElement = 0; idxElement < left.Length; idxElement++)
                {
                    if (left[idxElement] != right[idxElement])
                        return false;
                }

                return true;
            }

            /// <summary>
            /// Removes the specified field combination.
            /// </summary>
            /// <param name="fields">Two or more fields.</param>
            public void Remove(string[] fields)
            {
                // FieldPermissions are sorted in SharedControlDef.
                string[] sortedFields = (string[])fields.Clone();
                Array.Sort<string>(sortedFields);

                int removeAt = -1;
                for (int idx = 0; idx < this.sharings.Count; idx++)
                {
                    SharedControlDef currShare = this.sharings[idx];
                    if (ArraysAreEqual(sortedFields, currShare.Fields))
                    {
                        removeAt = idx;
                        break;
                    }
                }

                if (removeAt >= 0)
                    this.sharings.RemoveAt(removeAt);
            }

            /// <summary>
            /// Clears all sharing definitions.
            /// </summary>
            public void Clear()
            {
                this.sharings.Clear();
            }

            /// <summary>
            /// Gets the number of sharing defined by the current instance.
            /// </summary>
            public int Count
            {
                get { return this.sharings.Count; }
            }

            /// <summary>
            /// Gets field combinations.
            /// </summary>
            /// <returns>Array of string arrays which contain field names.</returns>
            public string[][] GetCombinations()
            {
                string[][] combinations = new string[this.sharings.Count][];
                for (int idxCombination = 0; idxCombination < combinations.Length; idxCombination++)
                    combinations[idxCombination] = this.sharings[idxCombination].Fields.Clone() as string[];

                return combinations;
            }

            #endregion

            #region ICloneable.

            /// <summary>
            /// Creates a new object that is a <b>deep</b> copy of the current instance.
            /// </summary>
            /// <returns>A new object that is a copy of this instance.</returns>
            public object Clone()
            {
                ControlSharingList clone = new ControlSharingList();
                foreach (SharedControlDef combination in this.sharings)
                    clone.sharings.Add(combination);

                return clone;
            }

            #endregion
        }

        internal class SharedControlDef
        {
            public string[] Fields;
            public string Caption;
            public string Description;

            public SharedControlDef(string[] fields, string caption, string description)
            {
                this.Fields = (string[])fields.Clone();
                Array.Sort<string>(this.Fields);

                this.Caption = caption;
                this.Description = description;
            }
        }

        #endregion

        #region IntervalFlags.

        static IntervalFlags defaultIntervalCriteria = IntervalFlags.Default;

        /// <summary>
        /// Gets or sets default value for <see cref="IntervalCriteria"/> property.
        /// </summary>
        /// <value><see cref="IntervalFlags"/> combination. Default is <see cref="IntervalFlags.Default"/>.</value>
        public static IntervalFlags DefaultIntervalCriteria
        {
            get { return defaultIntervalCriteria; }
            set { defaultIntervalCriteria = value; }
        }

        IntervalFlags intervalCriteria = DefaultIntervalCriteria;

        /// <summary>
        /// Gets or sets the flags that control when interval controls are generated. 
        /// </summary>
        /// <value><see cref="IntervalFlags"/> combination. Default is defined in static <see cref="DefaultIntervalCriteria"/> property.</value>
        [Browsable(false)]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public virtual IntervalFlags IntervalCriteria
        {
            get { return this.intervalCriteria; }
            set { this.intervalCriteria = value; }
        }

        /// <summary>
        /// Specifies flags that control when interval controls are generated.
        /// </summary>
        [Flags]
        public enum IntervalFlags : uint
        {
            /// <summary>
            /// Specifies that interval controls are generated if a search field accepts date values. Included in <see cref="Default"/>.
            /// </summary>
            Date = 1,

            /// <summary>
            /// Specifies that interval controls are generated if a search field accepts integer values. Included in <see cref="Default"/>.
            /// </summary>
            Integer = 2,

            /// <summary>
            /// Specifies that interval controls are generated if a search field accepts floating point values. Included in <see cref="Default"/>.
            /// </summary>
            FloatingPointNumber = 4,

            /// <summary>
            /// Specifies that interval controls are generated if a search field accepts textual values. Included in <see cref="Default"/>.
            /// </summary>
            Text = 8,

            /// <summary>
            /// Specifies that interval controls are generated if <see cref="IBindableField.CodeList"/> is defined for a search field.
            /// </summary>
            CodeList = 16,

            /// <summary>
            /// Specifies that interval controls are generated if <see cref="ISearchEngineField.LookupBuilder"/> is defined for a search field.
            /// </summary>
            Lookup = 32,

            /// <summary>
            /// Specifies that <see cref="ISearchEngineField.AllowIntervalSearch"/> property is obeyed if it's set to <b>false</b>. Included in <see cref="Default"/>.
            /// </summary>
            ObeyAllowIntervalSearchIfFalse = 64,

            /// <summary>
            /// Specifies that <see cref="ISearchEngineField.AllowIntervalSearch"/> property is obeyed if it's set to <b>true</b>. Included in <see cref="Default"/>.
            /// </summary>
            ObeyAllowIntervalSearchIfTrue = 128,

            /// <summary>
            /// Specifies no flags. Interval controls will not be generated.
            /// </summary>
            Never = 0,

            /// <summary>
            /// Specifies <see cref="Date"/>, <see cref="Integer"/>, <see cref="FloatingPointNumber"/>, <see cref="Text"/>, <see cref="ObeyAllowIntervalSearchIfFalse"/> and <see cref="ObeyAllowIntervalSearchIfTrue"/> flags.
            /// </summary>
            Default = Date | Integer | FloatingPointNumber | Text | ObeyAllowIntervalSearchIfFalse | ObeyAllowIntervalSearchIfTrue,

            /// <summary>
            /// Specifies <see cref="Date"/>, <see cref="Integer"/>, <see cref="FloatingPointNumber"/> and <see cref="ObeyAllowIntervalSearchIfFalse"/> flags.
            /// </summary>
            DefaultExceptText = Date | Integer | FloatingPointNumber | ObeyAllowIntervalSearchIfFalse,

            /// <summary>
            /// Specifies all flags. Interval controls are always generated.
            /// </summary>
            Always = 0xFFFFFFFF
        }

        private static class IntervalFlagsUtil
        {
            public static bool ShouldCreateInterval(ISearchEngineField field, IntervalFlags criteria)
            {
                // No need to inspect flags if Never or Always is defined.
                if (IsNever(criteria))
                    return false;
                if (IsAlways(criteria))
                    return true;

                // AllowIntervalSearch property is obeyed only if ObeyAllowIntervalSearchIfFalse or ObeyAllowIntervalSearchIfTrue flags are set.
                if (!field.AllowIntervalSearch && IsSet(criteria, IntervalFlags.ObeyAllowIntervalSearchIfFalse))
                    return false;
                if (field.AllowIntervalSearch && IsSet(criteria, IntervalFlags.ObeyAllowIntervalSearchIfTrue))
                    return true;

                if (field.CodeList != null)
                {
                    if (!IsSet(criteria, IntervalFlags.CodeList))
                        return false;
                    else
                        return IsAllowedForType(criteria, field.DataType);                    
                }
                else if (field.LookupBuilder != null)
                {
                    if (!IsSet(criteria, IntervalFlags.Lookup))
                        return false;
                    else
                        return IsAllowedForType(criteria, field.DataType);
                }
                else
                {
                    return IsAllowedForType(criteria, field.DataType);
                }
            }

            private static bool IsNever(IntervalFlags criteria)
            {
                return (criteria == IntervalFlags.Never);
            }

            private static bool IsAlways(IntervalFlags criteria)
            {
                return (criteria == IntervalFlags.Always);
            }

            private static bool IsSet(IntervalFlags criteria, IntervalFlags flag)
            {
                return (criteria & flag) > 0;
            }

            private static bool IsAllowedForType(IntervalFlags criteria, Type type)
            {
                if (TypeUtil.IsText(type))
                    return IsSet(criteria, IntervalFlags.Text);
                else if (TypeUtil.IsInteger(type))
                    return IsSet(criteria, IntervalFlags.Integer);
                else if (TypeUtil.IsDate(type))
                    return IsSet(criteria, IntervalFlags.Date);
                else if (TypeUtil.IsFloatingPointNumber(type))
                    return IsSet(criteria, IntervalFlags.FloatingPointNumber);
                else
                    return false;                
            }
        }

        #endregion

        #region TextSource - instantiated by internal SearchFilterTextSourceFactory class.

        ISearchFilterTextSource textSource = SearchFilterTextSourceFactory.GetTextSource();

        /// <summary>Gets source of captions and messages configured in application's settings file. Default is <see cref="SearchFilterTextSource"/> </summary>
        /// <value>An <see cref="ISearchFilterTextSource"/> object which contains captions and messages that override values set through SearchFilter properties.</value>
        /// <remarks><para>Settings are read from the application's config file as shown in the following example:
        /// <code>
        /// <configuration>
        ///     <appSettings>
        ///         <add key="BizElements.Web.SearchFilter.TextSourceAssembly" value="BizElements.Web" />
        ///         <add key="BizElements.Web.SearchFilter.TextSourceClass" value="BizElements.Web.SearchFilterTextSource" />
        ///     </appSettings>
        /// </configuration>
        /// </code></para>
        /// <para>If the previus settings are not found in cofiguration file, then a dummy implementation which doesn't override SearchFilter's properties
        /// is used, i.e. an implementation that returns null values.</para>
        /// </remarks>
        public ISearchFilterTextSource TextSource
        {
            get { return this.textSource; }
        }

        #endregion
    }
}